/**
 * Created by user on 2020/3/7.
 */

const BoundingSphere  = Cesium.BoundingSphere;
const Cartesian2  = Cesium.Cartesian2;
const Cartesian3  = Cesium.Cartesian3;
const Cartesian4  = Cesium.Cartesian4;
const Cartographic  = Cesium.Cartographic;
const Check  = Cesium.Check;
const clone  = Cesium.clone;
const Color  = Cesium.Color;
const combine  = Cesium.combine;
const createGuid  = Cesium.createGuid;
const Credit  = Cesium.Credit;
const defaultValue  = Cesium.defaultValue;
const defined  = Cesium.defined;
const destroyObject  = Cesium.destroyObject;
const DeveloperError  = Cesium.DeveloperError;
const DistanceDisplayCondition  = Cesium.DistanceDisplayCondition;
const FeatureDetection  = Cesium.FeatureDetection;
const getAbsoluteUri  = Cesium.getAbsoluteUri;
const getMagic  = Cesium.getMagic;
const getStringFromTypedArray  = Cesium.getStringFromTypedArray;
const IndexDatatype  = Cesium.IndexDatatype;
const loadCRN  = Cesium.loadCRN;
const loadImageFromTypedArray  = Cesium.loadImageFromTypedArray;
const loadKTX  = Cesium.loadKTX;
const CesiumMath  = Cesium.Math;
const Matrix3  = Cesium.Matrix3;
const Matrix4  = Cesium.Matrix4;
const PixelFormat  = Cesium.PixelFormat;
const PrimitiveType  = Cesium.PrimitiveType;
const Quaternion  = Cesium.Quaternion;
const Resource  = Cesium.Resource;
const Transforms  = Cesium.Transforms;
const WebGLConstants  = Cesium.WebGLConstants;
const Buffer  = Cesium.Buffer;
const BufferUsage  = Cesium.BufferUsage;
const DrawCommand  = Cesium.DrawCommand;
const Pass  = Cesium.Pass;
const RenderState  = Cesium.RenderState;
const Sampler  = Cesium.Sampler;
const ShaderProgram  = Cesium.ShaderProgram;
const ShaderSource  = Cesium.ShaderSource;
const Texture  = Cesium.Texture;
const TextureMinificationFilter  = Cesium.TextureMinificationFilter;
const TextureWrap  = Cesium.TextureWrap;
const VertexArray  = Cesium.VertexArray;
const addDefaults  = Cesium.addDefaults;
const addPipelineExtras  = Cesium.addPipelineExtras;
const ForEach  = Cesium.ForEach;
const getAccessorByteStride  = Cesium.getAccessorByteStride;
const hasExtension  = Cesium.hasExtension;
const numberOfComponentsForType  = Cesium.numberOfComponentsForType;
const parseGlb  = Cesium.parseGlb;
const updateVersion  = Cesium.updateVersion;
const when  = Cesium.when;
const Axis  = Cesium.Axis;
const BlendingState  = Cesium.BlendingState;
const ClippingPlaneCollection  = Cesium.ClippingPlaneCollection;
const ColorBlendMode  = Cesium.ColorBlendMode;
const DepthFunction  = Cesium.DepthFunction;
const DracoLoader  = Cesium.DracoLoader;
const getClipAndStyleCode  = Cesium.getClipAndStyleCode;
const getClippingFunction  = Cesium.getClippingFunction;
const HeightReference  = Cesium.HeightReference;
const JobType  = Cesium.JobType;
const ModelAnimationCache  = Cesium.ModelAnimationCache;
const ModelAnimationCollection  = Cesium.ModelAnimationCollection;
const ModelLoadResources  = Cesium.ModelLoadResources;
const ModelMaterial  = Cesium.ModelMaterial;
const ModelMesh  = Cesium.ModelMesh;
const ModelNode  = Cesium.ModelNode;
const ModelUtility  = Cesium.ModelUtility;
const OctahedralProjectedCubeMap  = Cesium.OctahedralProjectedCubeMap;
const processModelMaterialsCommon  = Cesium.processModelMaterialsCommon;
const processPbrMaterials  = Cesium.processPbrMaterials;
const SceneMode  = Cesium.SceneMode;
const ShadowMode  = Cesium.ShadowMode;
const Model = Cesium.Model;

var boundingSphereCartesian3Scratch = new Cartesian3();

var ModelState = ModelUtility.ModelState;

// glTF MIME types discussed in https://github.com/KhronosGroup/glTF/issues/412 and https://github.com/KhronosGroup/glTF/issues/943
var defaultModelAccept = 'model/gltf-binary,model/gltf+json;q=0.8,application/json;q=0.2,*/*;q=0.01';

var articulationEpsilon = CesiumMath.EPSILON16;

///////////////////////////////////////////////////////////////////////////

function setCachedGltf(model, cachedGltf) {
    model._cachedGltf = cachedGltf;
}

// glTF JSON can be big given embedded geometry, textures, and animations, so we
// cache it across all models using the same url/cache-key.  This also reduces the
// slight overhead in assigning defaults to missing values.
//
// Note that this is a global cache, compared to renderer resources, which
// are cached per context.
function CachedGltf(options) {
    this._gltf = options.gltf;
    this.ready = options.ready;
    this.modelsToLoad = [];
    this.count = 0;
}

Object.defineProperties(CachedGltf.prototype, {
    gltf : {
        set : function(value) {
            this._gltf = value;
        },

        get : function() {
            return this._gltf;
        }
    }
});

CachedGltf.prototype.makeReady = function(gltfJson) {
    this.gltf = gltfJson;

    var models = this.modelsToLoad;
    var length = models.length;
    for (var i = 0; i < length; ++i) {
        var m = models[i];
        if (!m.isDestroyed()) {
            setCachedGltf(m, this);
        }
    }
    this.modelsToLoad = undefined;
    this.ready = true;
};

var gltfCache = {};
var uriToGuid = {};
///////////////////////////////////////////////////////////////////////////

/**
 * A 3D model based on glTF, the runtime asset format for WebGL, OpenGL ES, and OpenGL.
 * <p>
 * Cesium includes support for geometry and materials, glTF animations, and glTF skinning.
 * In addition, individual glTF nodes are pickable with {@link Scene#pick} and animatable
 * with {@link Model#getNode}.  glTF cameras and lights are not currently supported.
 * </p>
 * <p>
 * An external glTF asset is created with {@link Model.fromGltf}.  glTF JSON can also be
 * created at runtime and passed to this constructor function.  In either case, the
 * {@link Model#readyPromise} is resolved when the model is ready to render, i.e.,
 * when the external binary, image, and shader files are downloaded and the WebGL
 * resources are created.
 * </p>
 * <p>
 * Cesium supports glTF assets with the following extensions:
 * <ul>
 * <li>
 * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/1.0/Khronos/KHR_binary_glTF/README.md|KHR_binary_glTF (glTF 1.0)}
 * </li><li>
 * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/1.0/Khronos/KHR_materials_common/README.md|KHR_materials_common (glTF 1.0)}
 * </li><li>
 * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/1.0/Vendor/WEB3D_quantized_attributes/README.md|WEB3D_quantized_attributes (glTF 1.0)}
 * </li><li>
 * {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/AGI_articulations/README.md|AGI_articulations}
 * </li><li>
 * {@link https://github.com/KhronosGroup/glTF/pull/1302|KHR_blend (draft)}
 * </li><li>
 * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_draco_mesh_compression/README.md|KHR_draco_mesh_compression}
 * </li><li>
 * {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/README.md|KHR_materials_pbrSpecularGlossiness}
 * </li><li>
 * {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit/README.md|KHR_materials_unlit}
 * </li><li>
 * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_techniques_webgl/README.md|KHR_techniques_webgl}
 * </li><li>
 * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_texture_transform/README.md|KHR_texture_transform}
 * </li>
 * </ul>
 * </p>
 * <p>
 * For high-precision rendering, Cesium supports the {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/1.0/Vendor/CESIUM_RTC/README.md|CESIUM_RTC} extension, which introduces the
 * CESIUM_RTC_MODELVIEW parameter semantic that says the node is in WGS84 coordinates translated
 * relative to a local origin.
 * </p>
 *
 * @alias Model
 * @constructor
 *
 * @param {Object} [options] Object with the following properties:
 * @param {Object|ArrayBuffer|Uint8Array} [options.gltf] A glTF JSON object, or a binary glTF buffer.
 * @param {Resource|String} [options.basePath=''] The base path that paths in the glTF JSON are relative to.
 * @param {Boolean} [options.show=true] Determines if the model primitive will be shown.
 * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the model from model to world coordinates.
 * @param {Number} [options.scale=1.0] A uniform scale applied to this model.
 * @param {Number} [options.minimumPixelSize=0.0] The approximate minimum pixel size of the model regardless of zoom.
 * @param {Number} [options.maximumScale] The maximum scale size of a model. An upper limit for minimumPixelSize.
 * @param {Object} [options.id] A user-defined object to return when the model is picked with {@link Scene#pick}.
 * @param {Boolean} [options.allowPicking=true] When <code>true</code>, each glTF mesh and primitive is pickable with {@link Scene#pick}.
 * @param {Boolean} [options.incrementallyLoadTextures=true] Determine if textures may continue to stream in after the model is loaded.
 * @param {Boolean} [options.asynchronous=true] Determines if model WebGL resource creation will be spread out over several frames or block until completion once all glTF files are loaded.
 * @param {Boolean} [options.clampAnimations=true] Determines if the model's animations should hold a pose over frames where no keyframes are specified.
 * @param {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the model casts or receives shadows from light sources.
 * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for each draw command in the model.
 * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the model in wireframe.
 * @param {HeightReference} [options.heightReference=HeightReference.NONE] Determines how the model is drawn relative to terrain.
 * @param {Scene} [options.scene] Must be passed in for models that use the height reference property.
 * @param {DistanceDisplayCondition} [options.distanceDisplayCondition] The condition specifying at what distance from the camera that this model will be displayed.
 * @param {Color} [options.color=Color.WHITE] A color that blends with the model's rendered color.
 * @param {ColorBlendMode} [options.colorBlendMode=ColorBlendMode.HIGHLIGHT] Defines how the color blends with the model.
 * @param {Number} [options.colorBlendAmount=0.5] Value used to determine the color strength when the <code>colorBlendMode</code> is <code>MIX</code>. A value of 0.0 results in the model's rendered color while a value of 1.0 results in a solid color, with any value in-between resulting in a mix of the two.
 * @param {Color} [options.silhouetteColor=Color.RED] The silhouette color. If more than 256 models have silhouettes enabled, there is a small chance that overlapping models will have minor artifacts.
 * @param {Number} [options.silhouetteSize=0.0] The size of the silhouette in pixels.
 * @param {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the model.
 * @param {Boolean} [options.dequantizeInShader=true] Determines if a {@link https://github.com/google/draco|Draco} encoded model is dequantized on the GPU. This decreases total memory usage for encoded models.
 * @param {Cartesian2} [options.imageBasedLightingFactor=Cartesian2(1.0, 1.0)] Scales diffuse and specular image-based lighting from the earth, sky, atmosphere and star skybox.
 * @param {Cartesian3} [options.lightColor] The light color when shading the model. When <code>undefined</code> the scene's light color is used instead.
 * @param {Number} [options.luminanceAtZenith=0.2] The sun's luminance at the zenith in kilo candela per meter squared to use for this model's procedural environment map.
 * @param {Cartesian3[]} [options.sphericalHarmonicCoefficients] The third order spherical harmonic coefficients used for the diffuse color of image-based lighting.
 * @param {String} [options.specularEnvironmentMaps] A URL to a KTX file that contains a cube map of the specular lighting and the convoluted specular mipmaps.
 * @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas.
 *
 * @see Model.fromGltf
 *
 * @demo {@link https://sandcastle.cesium.com/index.html?src=3D%20Models.html|Cesium Sandcastle Models Demo}
 */




function silhouetteSupported(context) {
    return context.stencilBuffer;
}

function isColorShadingEnabled(model) {
    return !Color.equals(model.color, Color.WHITE) || (model.colorBlendMode !== ColorBlendMode.HIGHLIGHT);
}

function isClippingEnabled(model) {
    var clippingPlanes = model._clippingPlanes;
    return defined(clippingPlanes) && clippingPlanes.enabled && clippingPlanes.length !== 0;
}


function containsGltfMagic(uint8Array) {
    var magic = getMagic(uint8Array);
    return magic === 'glTF';
}




function getRuntime(model, runtimeName, name) {
    //>>includeStart('debug', pragmas.debug);
    if (model._state !== ModelState.LOADED) {
        throw new DeveloperError('The model is not loaded.  Use Model.readyPromise or wait for Model.ready to be true.');
    }

    if (!defined(name)) {
        throw new DeveloperError('name is required.');
    }
    //>>includeEnd('debug');

    return (model._runtime[runtimeName])[name];
}









var scratchArticulationCartesian = new Cartesian3();
var scratchArticulationRotation = new Matrix3();

/**
 * Modifies a Matrix4 by applying a transformation for a given value of a stage.  Note this is different usage
 * from the typical <code>result</code> parameter, in that the incoming value of <code>result</code> is
 * meaningful.  Various stages of an articulation can be multiplied together, so their
 * transformations are all merged into a composite Matrix4 representing them all.
 *
 * @param {object} stage The stage of an articulation that is being evaluated.
 * @param {Matrix4} result The matrix to be modified.
 * @returns {Matrix4} A matrix transformed as requested by the articulation stage.
 *
 * @private
 */
function applyArticulationStageMatrix(stage, result) {
    //>>includeStart('debug', pragmas.debug);
    Check.typeOf.object('stage', stage);
    Check.typeOf.object('result', result);
    //>>includeEnd('debug');

    var value = stage.currentValue;
    var cartesian = scratchArticulationCartesian;
    var rotation;
    switch (stage.type) {
        case 'xRotate':
            rotation = Matrix3.fromRotationX(CesiumMath.toRadians(value), scratchArticulationRotation);
            Matrix4.multiplyByMatrix3(result, rotation, result);
            break;
        case 'yRotate':
            rotation = Matrix3.fromRotationY(CesiumMath.toRadians(value), scratchArticulationRotation);
            Matrix4.multiplyByMatrix3(result, rotation, result);
            break;
        case 'zRotate':
            rotation = Matrix3.fromRotationZ(CesiumMath.toRadians(value), scratchArticulationRotation);
            Matrix4.multiplyByMatrix3(result, rotation, result);
            break;
        case 'xTranslate':
            cartesian.x = value;
            cartesian.y = 0.0;
            cartesian.z = 0.0;
            Matrix4.multiplyByTranslation(result, cartesian, result);
            break;
        case 'yTranslate':
            cartesian.x = 0.0;
            cartesian.y = value;
            cartesian.z = 0.0;
            Matrix4.multiplyByTranslation(result, cartesian, result);
            break;
        case 'zTranslate':
            cartesian.x = 0.0;
            cartesian.y = 0.0;
            cartesian.z = value;
            Matrix4.multiplyByTranslation(result, cartesian, result);
            break;
        case 'xScale':
            cartesian.x = value;
            cartesian.y = 1.0;
            cartesian.z = 1.0;
            Matrix4.multiplyByScale(result, cartesian, result);
            break;
        case 'yScale':
            cartesian.x = 1.0;
            cartesian.y = value;
            cartesian.z = 1.0;
            Matrix4.multiplyByScale(result, cartesian, result);
            break;
        case 'zScale':
            cartesian.x = 1.0;
            cartesian.y = 1.0;
            cartesian.z = value;
            Matrix4.multiplyByScale(result, cartesian, result);
            break;
        case 'uniformScale':
            Matrix4.multiplyByUniformScale(result, value, result);
            break;
        default:
            break;
    }
    return result;
}

var scratchApplyArticulationTransform = new Matrix4();



///////////////////////////////////////////////////////////////////////////

function addBuffersToLoadResources(model) {
    var gltf = model.gltf;
    var loadResources = model._loadResources;
    ForEach.buffer(gltf, function(buffer, id) {
        loadResources.buffers[id] = buffer.extras._pipeline.source;
    });
}

function bufferLoad(model, id) {
    return function(arrayBuffer) {
        var loadResources = model._loadResources;
        var buffer = new Uint8Array(arrayBuffer);
        --loadResources.pendingBufferLoads;
        model.gltf.buffers[id].extras._pipeline.source = buffer;
    };
}

function parseBufferViews(model) {
    var bufferViews = model.gltf.bufferViews;
    var vertexBuffersToCreate = model._loadResources.vertexBuffersToCreate;

    // Only ARRAY_BUFFER here.  ELEMENT_ARRAY_BUFFER created below.
    ForEach.bufferView(model.gltf, function(bufferView, id) {
        if (bufferView.target === WebGLConstants.ARRAY_BUFFER) {
            vertexBuffersToCreate.enqueue(id);
        }
    });

    var indexBuffersToCreate = model._loadResources.indexBuffersToCreate;
    var indexBufferIds = {};

    // The Cesium Renderer requires knowing the datatype for an index buffer
    // at creation type, which is not part of the glTF bufferview so loop
    // through glTF accessors to create the bufferview's index buffer.
    ForEach.accessor(model.gltf, function(accessor) {
        var bufferViewId = accessor.bufferView;
        if (!defined(bufferViewId)) {
            return;
        }

        var bufferView = bufferViews[bufferViewId];
        if ((bufferView.target === WebGLConstants.ELEMENT_ARRAY_BUFFER) && !defined(indexBufferIds[bufferViewId])) {
            indexBufferIds[bufferViewId] = true;
            indexBuffersToCreate.enqueue({
                id : bufferViewId,
                componentType : accessor.componentType
            });
        }
    });
}

function parseTechniques(model) {
    // retain references to gltf techniques
    var gltf = model.gltf;
    if (!hasExtension(gltf, 'KHR_techniques_webgl')) {
        return;
    }

    var sourcePrograms = model._sourcePrograms;
    var sourceTechniques = model._sourceTechniques;
    var programs = gltf.extensions.KHR_techniques_webgl.programs;

    ForEach.technique(gltf, function(technique, techniqueId) {
        sourceTechniques[techniqueId] = clone(technique);

        var programId = technique.program;
        if (!defined(sourcePrograms[programId])) {
            sourcePrograms[programId] = clone(programs[programId]);
        }
    });
}

function shaderLoad(model, type, id) {
    return function(source) {
        var loadResources = model._loadResources;
        loadResources.shaders[id] = {
            source : source,
            type : type,
            bufferView : undefined
        };
        --loadResources.pendingShaderLoads;
        model._rendererResources.sourceShaders[id] = source;
    };
}

function parseShaders(model) {
    var gltf = model.gltf;
    var buffers = gltf.buffers;
    var bufferViews = gltf.bufferViews;
    var sourceShaders = model._rendererResources.sourceShaders;
    ForEach.shader(gltf, function(shader, id) {
        // Shader references either uri (external or base64-encoded) or bufferView
        if (defined(shader.bufferView)) {
            var bufferViewId = shader.bufferView;
            var bufferView = bufferViews[bufferViewId];
            var bufferId = bufferView.buffer;
            var buffer = buffers[bufferId];
            var source = getStringFromTypedArray(buffer.extras._pipeline.source, bufferView.byteOffset, bufferView.byteLength);
            sourceShaders[id] = source;
        } else if (defined(shader.extras._pipeline.source)) {
            sourceShaders[id] = shader.extras._pipeline.source;
        } else {
            ++model._loadResources.pendingShaderLoads;

            var shaderResource = model._resource.getDerivedResource({
                url: shader.uri
            });

            shaderResource.fetchText()
                .then(shaderLoad(model, shader.type, id))
                .otherwise(ModelUtility.getFailedLoadFunction(model, 'shader', shaderResource.url));
        }
    });
}

function parsePrograms(model) {
    var sourceTechniques = model._sourceTechniques;
    for (var techniqueId in sourceTechniques) {
        if (sourceTechniques.hasOwnProperty(techniqueId)) {
            var technique = sourceTechniques[techniqueId];
            model._loadResources.programsToCreate.enqueue({
                programId: technique.program,
                techniqueId: techniqueId
            });
        }
    }
}

function parseArticulations(model) {
    var articulationsByName = {};
    var articulationsByStageKey = {};
    var runtimeStagesByKey = {};

    model._runtime.articulationsByName = articulationsByName;
    model._runtime.articulationsByStageKey = articulationsByStageKey;
    model._runtime.stagesByKey = runtimeStagesByKey;

    var gltf = model.gltf;
    if (!hasExtension(gltf, 'AGI_articulations') || !defined(gltf.extensions) || !defined(gltf.extensions.AGI_articulations)) {
        return;
    }

    var gltfArticulations = gltf.extensions.AGI_articulations.articulations;
    if (!defined(gltfArticulations)) {
        return;
    }

    var numArticulations = gltfArticulations.length;
    for (var i = 0; i < numArticulations; ++i) {
        var articulation = clone(gltfArticulations[i]);
        articulation.nodes = [];
        articulation.isDirty = true;
        articulationsByName[articulation.name] = articulation;

        var numStages = articulation.stages.length;
        for (var s = 0; s < numStages; ++s) {
            var stage = articulation.stages[s];
            stage.currentValue = stage.initialValue;

            var stageKey = articulation.name + ' ' + stage.name;
            articulationsByStageKey[stageKey] = articulation;
            runtimeStagesByKey[stageKey] = stage;
        }
    }
}

function imageLoad(model, textureId) {
    return function(image) {
        var loadResources = model._loadResources;
        --loadResources.pendingTextureLoads;
        loadResources.texturesToCreate.enqueue({
            id : textureId,
            image : image,
            bufferView : image.bufferView,
            width : image.width,
            height : image.height,
            internalFormat : image.internalFormat
        });
    };
}

var ktxRegex = /(^data:image\/ktx)|(\.ktx$)/i;
var crnRegex = /(^data:image\/crn)|(\.crn$)/i;

function parseTextures(model, context, supportsWebP) {
    var gltf = model.gltf;
    var images = gltf.images;
    var uri;
    ForEach.texture(gltf, function(texture, id) {
        var imageId = texture.source;

        if (defined(texture.extensions) && defined(texture.extensions.EXT_texture_webp) && supportsWebP) {
            imageId = texture.extensions.EXT_texture_webp.source;
        }

        var gltfImage = images[imageId];
        var extras = gltfImage.extras;

        var bufferViewId = gltfImage.bufferView;
        var mimeType = gltfImage.mimeType;
        uri = gltfImage.uri;

        // First check for a compressed texture
        if (defined(extras) && defined(extras.compressedImage3DTiles)) {
            var crunch = extras.compressedImage3DTiles.crunch;
            var s3tc = extras.compressedImage3DTiles.s3tc;
            var pvrtc = extras.compressedImage3DTiles.pvrtc1;
            var etc1 = extras.compressedImage3DTiles.etc1;

            if (context.s3tc && defined(crunch)) {
                mimeType = crunch.mimeType;
                if (defined(crunch.bufferView)) {
                    bufferViewId = crunch.bufferView;
                } else {
                    uri = crunch.uri;
                }
            } else if (context.s3tc && defined(s3tc)) {
                mimeType = s3tc.mimeType;
                if (defined(s3tc.bufferView)) {
                    bufferViewId = s3tc.bufferView;
                } else {
                    uri = s3tc.uri;
                }
            } else if (context.pvrtc && defined(pvrtc)) {
                mimeType = pvrtc.mimeType;
                if (defined(pvrtc.bufferView)) {
                    bufferViewId = pvrtc.bufferView;
                } else {
                    uri = pvrtc.uri;
                }
            } else if (context.etc1 && defined(etc1)) {
                mimeType = etc1.mimeType;
                if (defined(etc1.bufferView)) {
                    bufferViewId = etc1.bufferView;
                } else {
                    uri = etc1.uri;
                }
            }
        }

        // Image references either uri (external or base64-encoded) or bufferView
        if (defined(bufferViewId)) {
            model._loadResources.texturesToCreateFromBufferView.enqueue({
                id : id,
                image : undefined,
                bufferView : bufferViewId,
                mimeType : mimeType
            });
        } else {
            ++model._loadResources.pendingTextureLoads;

            var imageResource = model._resource.getDerivedResource({
                url : uri
            });

            var promise;
            if (ktxRegex.test(uri)) {
                promise = loadKTX(imageResource);
            } else if (crnRegex.test(uri)) {
                promise = loadCRN(imageResource);
            } else {
                promise = imageResource.fetchImage();
            }
            promise.then(imageLoad(model, id, imageId)).otherwise(ModelUtility.getFailedLoadFunction(model, 'image', imageResource.url));
        }
    });
}

var scratchArticulationStageInitialTransform = new Matrix4();

function parseNodes(model) {
    var runtimeNodes = {};
    var runtimeNodesByName = {};
    var skinnedNodes = [];

    var skinnedNodesIds = model._loadResources.skinnedNodesIds;
    var articulationsByName = model._runtime.articulationsByName;

    ForEach.node(model.gltf, function(node, id) {
        var runtimeNode = {
            // Animation targets
            matrix : undefined,
            translation : undefined,
            rotation : undefined,
            scale : undefined,

            // Per-node show inherited from parent
            computedShow : true,

            // Computed transforms
            transformToRoot : new Matrix4(),
            computedMatrix : new Matrix4(),
            dirtyNumber : 0,                    // The frame this node was made dirty by an animation; for graph traversal

            // Rendering
            commands : [],                      // empty for transform, light, and camera nodes

            // Skinned node
            inverseBindMatrices : undefined,    // undefined when node is not skinned
            bindShapeMatrix : undefined,        // undefined when node is not skinned or identity
            joints : [],                        // empty when node is not skinned
            computedJointMatrices : [],         // empty when node is not skinned

            // Joint node
            jointName : node.jointName,         // undefined when node is not a joint

            weights : [],

            // Graph pointers
            children : [],                      // empty for leaf nodes
            parents : [],                       // empty for root nodes

            // Publicly-accessible ModelNode instance to modify animation targets
            publicNode : undefined
        };
        runtimeNode.publicNode = new ModelNode(model, node, runtimeNode, id, ModelUtility.getTransform(node));

        runtimeNodes[id] = runtimeNode;
        runtimeNodesByName[node.name] = runtimeNode;

        if (defined(node.skin)) {
            skinnedNodesIds.push(id);
            skinnedNodes.push(runtimeNode);
        }

        if (defined(node.extensions) && defined(node.extensions.AGI_articulations)) {
            var articulationName = node.extensions.AGI_articulations.articulationName;
            if (defined(articulationName)) {
                var transform = Matrix4.clone(runtimeNode.publicNode.originalMatrix, scratchArticulationStageInitialTransform);
                var articulation = articulationsByName[articulationName];
                articulation.nodes.push(runtimeNode.publicNode);

                var numStages = articulation.stages.length;
                for (var s = 0; s < numStages; ++s) {
                    var stage = articulation.stages[s];
                    transform = applyArticulationStageMatrix(stage, transform);
                }
                runtimeNode.publicNode.matrix = transform;
            }
        }
    });

    model._runtime.nodes = runtimeNodes;
    model._runtime.nodesByName = runtimeNodesByName;
    model._runtime.skinnedNodes = skinnedNodes;
}

function parseMaterials(model) {
    var gltf = model.gltf;
    var techniques = model._sourceTechniques;

    var runtimeMaterialsByName = {};
    var runtimeMaterialsById = {};
    var uniformMaps = model._uniformMaps;

    ForEach.material(gltf, function(material, materialId) {
        // Allocated now so ModelMaterial can keep a reference to it.
        uniformMaps[materialId] = {
            uniformMap : undefined,
            values : undefined,
            jointMatrixUniformName : undefined,
            morphWeightsUniformName : undefined
        };

        var modelMaterial = new ModelMaterial(model, material, materialId);

        if (defined(material.extensions) && defined(material.extensions.KHR_techniques_webgl)) {
            var techniqueId = material.extensions.KHR_techniques_webgl.technique;
            modelMaterial._technique = techniqueId;
            modelMaterial._program = techniques[techniqueId].program;

            ForEach.materialValue(material, function(value, uniformName) {
                if (!defined(modelMaterial._values)) {
                    modelMaterial._values = {};
                }

                modelMaterial._values[uniformName] = clone(value);
            });
        }

        runtimeMaterialsByName[material.name] = modelMaterial;
        runtimeMaterialsById[materialId] = modelMaterial;
    });

    model._runtime.materialsByName = runtimeMaterialsByName;
    model._runtime.materialsById = runtimeMaterialsById;
}

function parseMeshes(model) {
    var runtimeMeshesByName = {};
    var runtimeMaterialsById = model._runtime.materialsById;

    ForEach.mesh(model.gltf, function(mesh, meshId) {
        runtimeMeshesByName[mesh.name] = new ModelMesh(mesh, runtimeMaterialsById, meshId);
        if (defined(model.extensionsUsed.WEB3D_quantized_attributes) || model._dequantizeInShader) {
            // Cache primitives according to their program
            ForEach.meshPrimitive(mesh, function(primitive, primitiveId) {
                var programId = getProgramForPrimitive(model, primitive);
                var programPrimitives = model._programPrimitives[programId];
                if (!defined(programPrimitives)) {
                    programPrimitives = {};
                    model._programPrimitives[programId] = programPrimitives;
                }
                programPrimitives[meshId + '.primitive.' + primitiveId] = primitive;
            });
        }
    });

    model._runtime.meshesByName = runtimeMeshesByName;
}

///////////////////////////////////////////////////////////////////////////

var CreateVertexBufferJob = function() {
    this.id = undefined;
    this.model = undefined;
    this.context = undefined;
};

CreateVertexBufferJob.prototype.set = function(id, model, context) {
    this.id = id;
    this.model = model;
    this.context = context;
};

CreateVertexBufferJob.prototype.execute = function() {
    createVertexBuffer(this.id, this.model, this.context);
};

///////////////////////////////////////////////////////////////////////////

function createVertexBuffer(bufferViewId, model, context) {
    var loadResources = model._loadResources;
    var bufferViews = model.gltf.bufferViews;
    var bufferView = bufferViews[bufferViewId];

    // Use bufferView created at runtime
    if (!defined(bufferView)) {
        bufferView = loadResources.createdBufferViews[bufferViewId];
    }

    var vertexBuffer = Buffer.createVertexBuffer({
        context : context,
        typedArray : loadResources.getBuffer(bufferView),
        usage : BufferUsage.STATIC_DRAW
    });
    vertexBuffer.vertexArrayDestroyable = false;
    model._rendererResources.buffers[bufferViewId] = vertexBuffer;
    model._geometryByteLength += vertexBuffer.sizeInBytes;
}

///////////////////////////////////////////////////////////////////////////

var CreateIndexBufferJob = function() {
    this.id = undefined;
    this.componentType = undefined;
    this.model = undefined;
    this.context = undefined;
};

CreateIndexBufferJob.prototype.set = function(id, componentType, model, context) {
    this.id = id;
    this.componentType = componentType;
    this.model = model;
    this.context = context;
};

CreateIndexBufferJob.prototype.execute = function() {
    createIndexBuffer(this.id, this.componentType, this.model, this.context);
};

///////////////////////////////////////////////////////////////////////////

function createIndexBuffer(bufferViewId, componentType, model, context) {
    var loadResources = model._loadResources;
    var bufferViews = model.gltf.bufferViews;
    var bufferView = bufferViews[bufferViewId];

    // Use bufferView created at runtime
    if (!defined(bufferView)) {
        bufferView = loadResources.createdBufferViews[bufferViewId];
    }

    var indexBuffer = Buffer.createIndexBuffer({
        context : context,
        typedArray : loadResources.getBuffer(bufferView),
        usage : BufferUsage.STATIC_DRAW,
        indexDatatype : componentType
    });
    indexBuffer.vertexArrayDestroyable = false;
    model._rendererResources.buffers[bufferViewId] = indexBuffer;
    model._geometryByteLength += indexBuffer.sizeInBytes;
}

var scratchVertexBufferJob = new CreateVertexBufferJob();
var scratchIndexBufferJob = new CreateIndexBufferJob();

function createBuffers(model, frameState) {
    var loadResources = model._loadResources;

    if (loadResources.pendingBufferLoads !== 0) {
        return;
    }

    var context = frameState.context;
    var vertexBuffersToCreate = loadResources.vertexBuffersToCreate;
    var indexBuffersToCreate = loadResources.indexBuffersToCreate;
    var i;

    if (model.asynchronous) {
        while (vertexBuffersToCreate.length > 0) {
            scratchVertexBufferJob.set(vertexBuffersToCreate.peek(), model, context);
            if (!frameState.jobScheduler.execute(scratchVertexBufferJob, JobType.BUFFER)) {
                break;
            }
            vertexBuffersToCreate.dequeue();
        }

        while (indexBuffersToCreate.length > 0) {
            i = indexBuffersToCreate.peek();
            scratchIndexBufferJob.set(i.id, i.componentType, model, context);
            if (!frameState.jobScheduler.execute(scratchIndexBufferJob, JobType.BUFFER)) {
                break;
            }
            indexBuffersToCreate.dequeue();
        }
    } else {
        while (vertexBuffersToCreate.length > 0) {
            createVertexBuffer(vertexBuffersToCreate.dequeue(), model, context);
        }

        while (indexBuffersToCreate.length > 0) {
            i = indexBuffersToCreate.dequeue();
            createIndexBuffer(i.id, i.componentType, model, context);
        }
    }
}

function getProgramForPrimitive(model, primitive) {
    var material = model._runtime.materialsById[primitive.material];
    if (!defined(material)) {
        return;
    }

    return material._program;
}

function modifyShaderForQuantizedAttributes(shader, programName, model) {
    var primitive;
    var primitives = model._programPrimitives[programName];

    // If no primitives were cached for this program, there's no need to modify the shader
    if (!defined(primitives)) {
        return shader;
    }

    var primitiveId;
    for (primitiveId in primitives) {
        if (primitives.hasOwnProperty(primitiveId)) {
            primitive = primitives[primitiveId];
            if (getProgramForPrimitive(model, primitive) === programName) {
                break;
            }
        }
    }

    // This is not needed after the program is processed, free the memory
    model._programPrimitives[programName] = undefined;

    var result;
    if (model.extensionsUsed.WEB3D_quantized_attributes) {
        result = ModelUtility.modifyShaderForQuantizedAttributes(model.gltf, primitive, shader);
        model._quantizedUniforms[programName] = result.uniforms;
    } else {
        var decodedData = model._decodedData[primitiveId];
        if (defined(decodedData)) {
            result = ModelUtility.modifyShaderForDracoQuantizedAttributes(model.gltf, primitive, shader, decodedData.attributes);
        } else {
            return shader;
        }
    }

    return result.shader;
}

function modifyShaderForColor(shader) {
    shader = ShaderSource.replaceMain(shader, 'gltf_blend_main');
    shader +=
        'uniform vec4 gltf_color; \n' +
        'uniform float gltf_colorBlend; \n' +
        'void main() \n' +
        '{ \n' +
        '    gltf_blend_main(); \n' +
        '    gl_FragColor.rgb = mix(gl_FragColor.rgb, gltf_color.rgb, gltf_colorBlend); \n' +
        '    float highlight = ceil(gltf_colorBlend); \n' +
        '    gl_FragColor.rgb *= mix(gltf_color.rgb, vec3(1.0), highlight); \n' +
        '    gl_FragColor.a *= gltf_color.a; \n' +
        '} \n';

    return shader;
}

function modifyShader(shader, programName, callback) {
    if (defined(callback)) {
        shader = callback(shader, programName);
    }
    return shader;
}

var CreateProgramJob = function() {
    this.programToCreate = undefined;
    this.model = undefined;
    this.context = undefined;
};

CreateProgramJob.prototype.set = function(programToCreate, model, context) {
    this.programToCreate = programToCreate;
    this.model = model;
    this.context = context;
};

CreateProgramJob.prototype.execute = function() {
    createProgram(this.programToCreate, this.model, this.context);
};

///////////////////////////////////////////////////////////////////////////

// When building programs for the first time, do not include modifiers for clipping planes and color
// since this is the version of the program that will be cached for use with other Models.
function createProgram(programToCreate, model, context) {
    var programId = programToCreate.programId;
    var techniqueId = programToCreate.techniqueId;
    var program = model._sourcePrograms[programId];
    var shaders = model._rendererResources.sourceShaders;

    var vs = shaders[program.vertexShader];
    var fs = shaders[program.fragmentShader];

    var quantizedVertexShaders = model._quantizedVertexShaders;
    var toClipCoordinatesGLSL = model._toClipCoordinatesGLSL[programId];

    if (model.extensionsUsed.WEB3D_quantized_attributes || model._dequantizeInShader) {
        var quantizedVS = quantizedVertexShaders[programId];
        if (!defined(quantizedVS)) {
            quantizedVS = modifyShaderForQuantizedAttributes(vs, programId, model);
            quantizedVertexShaders[programId] = quantizedVS;
        }
        vs = quantizedVS;
    }

    var drawVS = modifyShader(vs, programId, model._vertexShaderLoaded);
    var drawFS = modifyShader(fs, programId, model._fragmentShaderLoaded);

    // Internet Explorer seems to have problems with discard (for clipping planes) after too many levels of indirection:
    // https://github.com/CesiumGS/cesium/issues/6575.
    // For IE log depth code is defined out anyway due to unsupported WebGL extensions, so the wrappers can be omitted.
    if (!FeatureDetection.isInternetExplorer()) {
        drawVS = ModelUtility.modifyVertexShaderForLogDepth(drawVS, toClipCoordinatesGLSL);
        drawFS = ModelUtility.modifyFragmentShaderForLogDepth(drawFS);
    }

    if (!defined(model._uniformMapLoaded)) {
        drawFS = 'uniform vec4 czm_pickColor;\n' + drawFS;
    }

    var useIBL = model._imageBasedLightingFactor.x > 0.0 || model._imageBasedLightingFactor.y > 0.0;
    if (useIBL) {
        drawFS = '#define USE_IBL_LIGHTING \n\n' + drawFS;
    }

    if (defined(model._lightColor)) {
        drawFS = '#define USE_CUSTOM_LIGHT_COLOR \n\n' + drawFS;
    }

    if (model._sourceVersion !== '2.0' || model._sourceKHRTechniquesWebGL) {
        drawFS = ShaderSource.replaceMain(drawFS, 'non_gamma_corrected_main');
        drawFS =
            drawFS +
            '\n' +
            'void main() { \n' +
            '    non_gamma_corrected_main(); \n' +
            '    gl_FragColor = czm_gammaCorrect(gl_FragColor); \n' +
            '} \n';
    }

    if (OctahedralProjectedCubeMap.isSupported(context)) {
        var usesSH = defined(model._sphericalHarmonicCoefficients) || model._useDefaultSphericalHarmonics;
        var usesSM = (defined(model._specularEnvironmentMapAtlas) && model._specularEnvironmentMapAtlas.ready) || model._useDefaultSpecularMaps;
        var addMatrix = usesSH || usesSM || useIBL;
        if (addMatrix) {
            drawFS = 'uniform mat4 gltf_clippingPlanesMatrix; \n' + drawFS;
        }

        if (defined(model._sphericalHarmonicCoefficients)) {
            drawFS = '#define DIFFUSE_IBL \n' + '#define CUSTOM_SPHERICAL_HARMONICS \n' + 'uniform vec3 gltf_sphericalHarmonicCoefficients[9]; \n' + drawFS;
        } else if (model._useDefaultSphericalHarmonics) {
            drawFS = '#define DIFFUSE_IBL \n' + drawFS;
        }

        if (defined(model._specularEnvironmentMapAtlas) && model._specularEnvironmentMapAtlas.ready) {
            drawFS = '#define SPECULAR_IBL \n' + '#define CUSTOM_SPECULAR_IBL \n' + 'uniform sampler2D gltf_specularMap; \n' + 'uniform vec2 gltf_specularMapSize; \n' + 'uniform float gltf_maxSpecularLOD; \n' + drawFS;
        } else if (model._useDefaultSpecularMaps) {
            drawFS = '#define SPECULAR_IBL \n' + drawFS;
        }
    }

    if (defined(model._luminanceAtZenith)) {
        drawFS = '#define USE_SUN_LUMINANCE \n' + 'uniform float gltf_luminanceAtZenith;\n' + drawFS;
    }

    createAttributesAndProgram(programId, techniqueId, drawFS, drawVS, model, context);
}

function recreateProgram(programToCreate, model, context) {
    var programId = programToCreate.programId;
    var techniqueId = programToCreate.techniqueId;
    var program = model._sourcePrograms[programId];
    var shaders = model._rendererResources.sourceShaders;

    var quantizedVertexShaders = model._quantizedVertexShaders;
    var toClipCoordinatesGLSL = model._toClipCoordinatesGLSL[programId];

    var clippingPlaneCollection = model.clippingPlanes;
    var addClippingPlaneCode = isClippingEnabled(model);

    var vs = shaders[program.vertexShader];
    var fs = shaders[program.fragmentShader];

    if (model.extensionsUsed.WEB3D_quantized_attributes || model._dequantizeInShader) {
        vs = quantizedVertexShaders[programId];
    }

    var finalFS = fs;
    if (isColorShadingEnabled(model)) {
        finalFS = Model._modifyShaderForColor(finalFS);
    }
    if (addClippingPlaneCode) {
        finalFS = modifyShaderForClippingPlanes(finalFS, clippingPlaneCollection, context);
    }

    var drawVS = modifyShader(vs, programId, model._vertexShaderLoaded);
    var drawFS = modifyShader(finalFS, programId, model._fragmentShaderLoaded);

    if (!FeatureDetection.isInternetExplorer()) {
        drawVS = ModelUtility.modifyVertexShaderForLogDepth(drawVS, toClipCoordinatesGLSL);
        drawFS = ModelUtility.modifyFragmentShaderForLogDepth(drawFS);
    }

    if (!defined(model._uniformMapLoaded)) {
        drawFS = 'uniform vec4 czm_pickColor;\n' + drawFS;
    }

    var useIBL = model._imageBasedLightingFactor.x > 0.0 || model._imageBasedLightingFactor.y > 0.0;
    if (useIBL) {
        drawFS = '#define USE_IBL_LIGHTING \n\n' + drawFS;
    }

    if (defined(model._lightColor)) {
        drawFS = '#define USE_CUSTOM_LIGHT_COLOR \n\n' + drawFS;
    }

    if (model._sourceVersion !== '2.0' || model._sourceKHRTechniquesWebGL) {
        drawFS = ShaderSource.replaceMain(drawFS, 'non_gamma_corrected_main');
        drawFS =
            drawFS +
            '\n' +
            'void main() { \n' +
            '    non_gamma_corrected_main(); \n' +
            '    gl_FragColor = czm_gammaCorrect(gl_FragColor); \n' +
            '} \n';
    }

    if (OctahedralProjectedCubeMap.isSupported(context)) {
        var usesSH = defined(model._sphericalHarmonicCoefficients) || model._useDefaultSphericalHarmonics;
        var usesSM = (defined(model._specularEnvironmentMapAtlas) && model._specularEnvironmentMapAtlas.ready) || model._useDefaultSpecularMaps;
        var addMatrix = !addClippingPlaneCode && (usesSH || usesSM || useIBL);
        if (addMatrix) {
            drawFS = 'uniform mat4 gltf_clippingPlanesMatrix; \n' + drawFS;
        }

        if (defined(model._sphericalHarmonicCoefficients)) {
            drawFS = '#define DIFFUSE_IBL \n' + '#define CUSTOM_SPHERICAL_HARMONICS \n' + 'uniform vec3 gltf_sphericalHarmonicCoefficients[9]; \n' + drawFS;
        } else if (model._useDefaultSphericalHarmonics) {
            drawFS = '#define DIFFUSE_IBL \n' + drawFS;
        }

        if (defined(model._specularEnvironmentMapAtlas) && model._specularEnvironmentMapAtlas.ready) {
            drawFS = '#define SPECULAR_IBL \n' + '#define CUSTOM_SPECULAR_IBL \n' + 'uniform sampler2D gltf_specularMap; \n' + 'uniform vec2 gltf_specularMapSize; \n' + 'uniform float gltf_maxSpecularLOD; \n' + drawFS;
        } else if (model._useDefaultSpecularMaps) {
            drawFS = '#define SPECULAR_IBL \n' + drawFS;
        }
    }

    if (defined(model._luminanceAtZenith)) {
        drawFS = '#define USE_SUN_LUMINANCE \n' + 'uniform float gltf_luminanceAtZenith;\n' + drawFS;
    }

    createAttributesAndProgram(programId, techniqueId, drawFS, drawVS, model, context);
}

function createAttributesAndProgram(programId, techniqueId, drawFS, drawVS, model, context) {
    var technique = model._sourceTechniques[techniqueId];
    var attributeLocations = ModelUtility.createAttributeLocations(technique, model._precreatedAttributes);

    if(model._vertexShader){
        drawVS = ShaderSource.replaceMain(drawVS,'xh_vertexShader');
        drawVS += model._vertexShader;
    }

    if(model._fragmentShader){
        drawFS = ShaderSource.replaceMain(drawFS,'xh_fragmentShader');
        drawFS += model._fragmentShader;
    }

    if(model._showFS){
        console.log(drawFS);
    }

    if(model._showVS){
        console.log(drawVS);
    }

    model._rendererResources.programs[programId] = ShaderProgram.fromCache({
        context : context,
        vertexShaderSource : drawVS,
        fragmentShaderSource : drawFS,
        attributeLocations : attributeLocations
    });
}

var scratchCreateProgramJob = new CreateProgramJob();

function createPrograms(model, frameState) {
    var loadResources = model._loadResources;
    var programsToCreate = loadResources.programsToCreate;

    if (loadResources.pendingShaderLoads !== 0) {
        return;
    }

    // PERFORMANCE_IDEA: this could be more fine-grained by looking
    // at the shader's bufferView's to determine the buffer dependencies.
    if (loadResources.pendingBufferLoads !== 0) {
        return;
    }

    var context = frameState.context;

    if (model.asynchronous) {
        while (programsToCreate.length > 0) {
            scratchCreateProgramJob.set(programsToCreate.peek(), model, context);
            if (!frameState.jobScheduler.execute(scratchCreateProgramJob, JobType.PROGRAM)) {
                break;
            }
            programsToCreate.dequeue();
        }
    } else {
        // Create all loaded programs this frame
        while (programsToCreate.length > 0) {
            createProgram(programsToCreate.dequeue(), model, context);
        }
    }
}

function getOnImageCreatedFromTypedArray(loadResources, gltfTexture) {
    return function(image) {
        loadResources.texturesToCreate.enqueue({
            id : gltfTexture.id,
            image : image,
            bufferView : undefined
        });

        --loadResources.pendingBufferViewToImage;
    };
}

function loadTexturesFromBufferViews(model) {
    var loadResources = model._loadResources;

    if (loadResources.pendingBufferLoads !== 0) {
        return;
    }

    while (loadResources.texturesToCreateFromBufferView.length > 0) {
        var gltfTexture = loadResources.texturesToCreateFromBufferView.dequeue();

        var gltf = model.gltf;
        var bufferView = gltf.bufferViews[gltfTexture.bufferView];
        var imageId = gltf.textures[gltfTexture.id].source;

        var onerror = ModelUtility.getFailedLoadFunction(model, 'image', 'id: ' + gltfTexture.id + ', bufferView: ' + gltfTexture.bufferView);

        if (gltfTexture.mimeType === 'image/ktx') {
            loadKTX(loadResources.getBuffer(bufferView)).then(imageLoad(model, gltfTexture.id, imageId)).otherwise(onerror);
            ++model._loadResources.pendingTextureLoads;
        } else if (gltfTexture.mimeType === 'image/crn') {
            loadCRN(loadResources.getBuffer(bufferView)).then(imageLoad(model, gltfTexture.id, imageId)).otherwise(onerror);
            ++model._loadResources.pendingTextureLoads;
        } else {
            var onload = getOnImageCreatedFromTypedArray(loadResources, gltfTexture);
            loadImageFromTypedArray({
                uint8Array: loadResources.getBuffer(bufferView),
                format: gltfTexture.mimeType,
                flipY: false
            })
                .then(onload).otherwise(onerror);
            ++loadResources.pendingBufferViewToImage;
        }
    }
}

function createSamplers(model) {
    var loadResources = model._loadResources;
    if (loadResources.createSamplers) {
        loadResources.createSamplers = false;

        var rendererSamplers = model._rendererResources.samplers;
        ForEach.sampler(model.gltf, function(sampler, samplerId) {
            rendererSamplers[samplerId] = new Sampler({
                wrapS: sampler.wrapS,
                wrapT: sampler.wrapT,
                minificationFilter: sampler.minFilter,
                magnificationFilter: sampler.magFilter
            });
        });
    }
}

///////////////////////////////////////////////////////////////////////////

var CreateTextureJob = function() {
    this.gltfTexture = undefined;
    this.model = undefined;
    this.context = undefined;
};

CreateTextureJob.prototype.set = function(gltfTexture, model, context) {
    this.gltfTexture = gltfTexture;
    this.model = model;
    this.context = context;
};

CreateTextureJob.prototype.execute = function() {
    createTexture(this.gltfTexture, this.model, this.context);
};

///////////////////////////////////////////////////////////////////////////

function createTexture(gltfTexture, model, context) {
    var textures = model.gltf.textures;
    var texture = textures[gltfTexture.id];

    var rendererSamplers = model._rendererResources.samplers;
    var sampler = rendererSamplers[texture.sampler];
    if (!defined(sampler)) {
        sampler = new Sampler({
            wrapS : TextureWrap.REPEAT,
            wrapT : TextureWrap.REPEAT
        });
    }

    var usesTextureTransform = false;
    var materials = model.gltf.materials;
    var materialsLength = materials.length;
    for (var i = 0; i < materialsLength; ++i) {
        var material = materials[i];
        if (defined(material.extensions) && defined(material.extensions.KHR_techniques_webgl)) {
            var values = material.extensions.KHR_techniques_webgl.values;
            for (var valueName in values) {
                if (values.hasOwnProperty(valueName) && valueName.indexOf('Texture') !== -1) {
                    var value = values[valueName];
                    if (value.index === gltfTexture.id && defined(value.extensions) && defined(value.extensions.KHR_texture_transform)) {
                        usesTextureTransform = true;
                        break;
                    }
                }
            }
        }
        if (usesTextureTransform) {
            break;
        }
    }

    var wrapS = sampler.wrapS;
    var wrapT = sampler.wrapT;
    var minFilter = sampler.minificationFilter;

    if (usesTextureTransform && minFilter !== TextureMinificationFilter.LINEAR && minFilter !== TextureMinificationFilter.NEAREST) {
        if (minFilter === TextureMinificationFilter.NEAREST_MIPMAP_NEAREST || minFilter === TextureMinificationFilter.NEAREST_MIPMAP_LINEAR) {
            minFilter = TextureMinificationFilter.NEAREST;
        } else {
            minFilter = TextureMinificationFilter.LINEAR;
        }

        sampler = new Sampler({
            wrapS : sampler.wrapS,
            wrapT : sampler.wrapT,
            textureMinificationFilter : minFilter,
            textureMagnificationFilter : sampler.magnificationFilter
        });
    }

    var internalFormat = gltfTexture.internalFormat;

    var mipmap =
        (!(defined(internalFormat) && PixelFormat.isCompressedFormat(internalFormat))) &&
        ((minFilter === TextureMinificationFilter.NEAREST_MIPMAP_NEAREST) ||
        (minFilter === TextureMinificationFilter.NEAREST_MIPMAP_LINEAR) ||
        (minFilter === TextureMinificationFilter.LINEAR_MIPMAP_NEAREST) ||
        (minFilter === TextureMinificationFilter.LINEAR_MIPMAP_LINEAR));
    var requiresNpot = mipmap ||
        (wrapS === TextureWrap.REPEAT) ||
        (wrapS === TextureWrap.MIRRORED_REPEAT) ||
        (wrapT === TextureWrap.REPEAT) ||
        (wrapT === TextureWrap.MIRRORED_REPEAT);

    var tx;
    var source = gltfTexture.image;

    if (defined(internalFormat)) {
        tx = new Texture({
            context : context,
            source : {
                arrayBufferView : gltfTexture.bufferView
            },
            width : gltfTexture.width,
            height : gltfTexture.height,
            pixelFormat : internalFormat,
            sampler : sampler
        });
    } else if (defined(source)) {
        var npot = !CesiumMath.isPowerOfTwo(source.width) || !CesiumMath.isPowerOfTwo(source.height);

        if (requiresNpot && npot) {
            // WebGL requires power-of-two texture dimensions for mipmapping and REPEAT/MIRRORED_REPEAT wrap modes.
            var canvas = document.createElement('canvas');
            canvas.width = CesiumMath.nextPowerOfTwo(source.width);
            canvas.height = CesiumMath.nextPowerOfTwo(source.height);
            var canvasContext = canvas.getContext('2d');
            canvasContext.drawImage(source, 0, 0, source.width, source.height, 0, 0, canvas.width, canvas.height);
            source = canvas;
        }

        tx = new Texture({
            context : context,
            source : source,
            pixelFormat : texture.internalFormat,
            pixelDatatype : texture.type,
            sampler : sampler,
            flipY : false
        });
        // GLTF_SPEC: Support TEXTURE_CUBE_MAP.  https://github.com/KhronosGroup/glTF/issues/40
        if (mipmap) {
            tx.generateMipmap();
        }
    }
    if (defined(tx)) {
        model._rendererResources.textures[gltfTexture.id] = tx;
        model._texturesByteLength += tx.sizeInBytes;
    }
}

var scratchCreateTextureJob = new CreateTextureJob();

function createTextures(model, frameState) {
    var context = frameState.context;
    var texturesToCreate = model._loadResources.texturesToCreate;

    if (model.asynchronous) {
        while (texturesToCreate.length > 0) {
            scratchCreateTextureJob.set(texturesToCreate.peek(), model, context);
            if (!frameState.jobScheduler.execute(scratchCreateTextureJob, JobType.TEXTURE)) {
                break;
            }
            texturesToCreate.dequeue();
        }
    } else {
        // Create all loaded textures this frame
        while (texturesToCreate.length > 0) {
            createTexture(texturesToCreate.dequeue(), model, context);
        }
    }
}

function getAttributeLocations(model, primitive) {
    var techniques = model._sourceTechniques;

    // Retrieve the compiled shader program to assign index values to attributes
    var attributeLocations = {};

    var location;
    var index;
    var material = model._runtime.materialsById[primitive.material];
    if (!defined(material)) {
        return attributeLocations;
    }

    var technique = techniques[material._technique];
    if (!defined(technique)) {
        return attributeLocations;
    }

    var attributes = technique.attributes;
    var program = model._rendererResources.programs[technique.program];
    var programVertexAttributes = program.vertexAttributes;
    var programAttributeLocations = program._attributeLocations;

    // Note: WebGL shader compiler may have optimized and removed some attributes from programVertexAttributes
    for (location in programVertexAttributes) {
        if (programVertexAttributes.hasOwnProperty(location)) {
            var attribute = attributes[location];
            if (defined(attribute)) {
                index = programAttributeLocations[location];
                attributeLocations[attribute.semantic] = index;
            }
        }
    }

    // Always add pre-created attributes.
    // Some pre-created attributes, like per-instance pickIds, may be compiled out of the draw program
    // but should be included in the list of attribute locations for the pick program.
    // This is safe to do since programVertexAttributes and programAttributeLocations are equivalent except
    // that programVertexAttributes optimizes out unused attributes.
    var precreatedAttributes = model._precreatedAttributes;
    if (defined(precreatedAttributes)) {
        for (location in precreatedAttributes) {
            if (precreatedAttributes.hasOwnProperty(location)) {
                index = programAttributeLocations[location];
                attributeLocations[location] = index;
            }
        }
    }

    return attributeLocations;
}

function mapJointNames(forest, nodes) {
    var length = forest.length;
    var jointNodes = {};
    for (var i = 0; i < length; ++i) {
        var stack = [forest[i]]; // Push root node of tree

        while (stack.length > 0) {
            var id = stack.pop();
            var n = nodes[id];

            if (defined(n)) {
                jointNodes[id] = id;
            }

            var children = n.children;
            if (defined(children)) {
                var childrenLength = children.length;
                for (var k = 0; k < childrenLength; ++k) {
                    stack.push(children[k]);
                }
            }
        }
    }
    return jointNodes;
}

function createJoints(model, runtimeSkins) {
    var gltf = model.gltf;
    var skins = gltf.skins;
    var nodes = gltf.nodes;
    var runtimeNodes = model._runtime.nodes;

    var skinnedNodesIds = model._loadResources.skinnedNodesIds;
    var length = skinnedNodesIds.length;
    for (var j = 0; j < length; ++j) {
        var id = skinnedNodesIds[j];
        var skinnedNode = runtimeNodes[id];
        var node = nodes[id];

        var runtimeSkin = runtimeSkins[node.skin];
        skinnedNode.inverseBindMatrices = runtimeSkin.inverseBindMatrices;
        skinnedNode.bindShapeMatrix = runtimeSkin.bindShapeMatrix;

        // 1. Find nodes with the names in node.skeletons (the node's skeletons)
        // 2. These nodes form the root nodes of the forest to search for each joint in skin.jointNames.  This search uses jointName, not the node's name.
        // 3. Search for the joint name among the gltf node hierarchy instead of the runtime node hierarchy. Child links aren't set up yet for runtime nodes.
        var forest = [];
        var skin = skins[node.skin];
        if (defined(skin.skeleton)) {
            forest.push(skin.skeleton);
        }

        var mappedJointNames = mapJointNames(forest, nodes);
        var gltfJointNames = skins[node.skin].joints;
        var jointNamesLength = gltfJointNames.length;
        for (var i = 0; i < jointNamesLength; ++i) {
            var jointName = gltfJointNames[i];
            var nodeId = mappedJointNames[jointName];
            var jointNode = runtimeNodes[nodeId];
            skinnedNode.joints.push(jointNode);
        }
    }
}

function createSkins(model) {
    var loadResources = model._loadResources;

    if (loadResources.pendingBufferLoads !== 0) {
        return;
    }

    if (!loadResources.createSkins) {
        return;
    }
    loadResources.createSkins = false;

    var gltf = model.gltf;
    var accessors = gltf.accessors;
    var runtimeSkins = {};

    ForEach.skin(gltf, function(skin, id) {
        var accessor = accessors[skin.inverseBindMatrices];

        var bindShapeMatrix;
        if (!Matrix4.equals(skin.bindShapeMatrix, Matrix4.IDENTITY)) {
            bindShapeMatrix = Matrix4.clone(skin.bindShapeMatrix);
        }

        runtimeSkins[id] = {
            inverseBindMatrices : ModelAnimationCache.getSkinInverseBindMatrices(model, accessor),
            bindShapeMatrix : bindShapeMatrix // not used when undefined
        };
    });

    createJoints(model, runtimeSkins);
}

function getChannelEvaluator(model, runtimeNode, targetPath, spline) {
    return function(localAnimationTime) {
        //  Workaround for https://github.com/KhronosGroup/glTF/issues/219

        //if (targetPath === 'translation') {
        //    return;
        //}
        if (defined(spline)) {
            localAnimationTime = model.clampAnimations ? spline.clampTime(localAnimationTime) : spline.wrapTime(localAnimationTime);
            runtimeNode[targetPath] = spline.evaluate(localAnimationTime, runtimeNode[targetPath]);
            runtimeNode.dirtyNumber = model._maxDirtyNumber;
        }
    };
}

function createRuntimeAnimations(model) {
    var loadResources = model._loadResources;

    if (!loadResources.finishedPendingBufferLoads()) {
        return;
    }

    if (!loadResources.createRuntimeAnimations) {
        return;
    }
    loadResources.createRuntimeAnimations = false;

    model._runtime.animations = [];

    var runtimeNodes = model._runtime.nodes;
    var accessors = model.gltf.accessors;

    ForEach.animation(model.gltf, function (animation, i) {
        var channels = animation.channels;
        var samplers = animation.samplers;

        // Find start and stop time for the entire animation
        var startTime = Number.MAX_VALUE;
        var stopTime = -Number.MAX_VALUE;

        var channelsLength = channels.length;
        var channelEvaluators = new Array(channelsLength);

        for (var j = 0; j < channelsLength; ++j) {
            var channel = channels[j];
            var target = channel.target;
            var path = target.path;
            var sampler = samplers[channel.sampler];
            var input = ModelAnimationCache.getAnimationParameterValues(model, accessors[sampler.input]);
            var output = ModelAnimationCache.getAnimationParameterValues(model, accessors[sampler.output]);

            startTime = Math.min(startTime, input[0]);
            stopTime = Math.max(stopTime, input[input.length - 1]);

            var spline = ModelAnimationCache.getAnimationSpline(model, i, animation, channel.sampler, sampler, input, path, output);

            // GLTF_SPEC: Support more targets like materials. https://github.com/KhronosGroup/glTF/issues/142
            channelEvaluators[j] = getChannelEvaluator(model, runtimeNodes[target.node], target.path, spline);
        }

        model._runtime.animations[i] = {
            name : animation.name,
            startTime : startTime,
            stopTime : stopTime,
            channelEvaluators : channelEvaluators
        };
    });
}

function createVertexArrays(model, context) {
    var loadResources = model._loadResources;
    if (!loadResources.finishedBuffersCreation() || !loadResources.finishedProgramCreation()
        || !loadResources.createVertexArrays) {
        return;
    }
    loadResources.createVertexArrays = false;

    var rendererBuffers = model._rendererResources.buffers;
    var rendererVertexArrays = model._rendererResources.vertexArrays;
    var gltf = model.gltf;
    var accessors = gltf.accessors;
    ForEach.mesh(gltf, function(mesh, meshId) {
        ForEach.meshPrimitive(mesh, function(primitive, primitiveId) {
            var attributes = [];
            var attributeLocation;
            var attributeLocations = getAttributeLocations(model, primitive);
            var decodedData = model._decodedData[meshId + '.primitive.' + primitiveId];
            ForEach.meshPrimitiveAttribute(primitive, function(accessorId, attributeName) {
                // Skip if the attribute is not used by the material, e.g., because the asset
                // was exported with an attribute that wasn't used and the asset wasn't optimized.
                attributeLocation = attributeLocations[attributeName];
                if (defined(attributeLocation)) {
                    // Use attributes of previously decoded draco geometry
                    if (defined(decodedData)) {
                        var decodedAttributes = decodedData.attributes;
                        if (decodedAttributes.hasOwnProperty(attributeName)) {
                            var decodedAttribute = decodedAttributes[attributeName];
                            attributes.push({
                                index: attributeLocation,
                                vertexBuffer: rendererBuffers[decodedAttribute.bufferView],
                                componentsPerAttribute: decodedAttribute.componentsPerAttribute,
                                componentDatatype: decodedAttribute.componentDatatype,
                                normalize: decodedAttribute.normalized,
                                offsetInBytes: decodedAttribute.byteOffset,
                                strideInBytes: decodedAttribute.byteStride
                            });

                            return;
                        }
                    }

                    var a = accessors[accessorId];
                    var normalize = defined(a.normalized) && a.normalized;
                    attributes.push({
                        index: attributeLocation,
                        vertexBuffer: rendererBuffers[a.bufferView],
                        componentsPerAttribute: numberOfComponentsForType(a.type),
                        componentDatatype: a.componentType,
                        normalize: normalize,
                        offsetInBytes: a.byteOffset,
                        strideInBytes: getAccessorByteStride(gltf, a)
                    });
                }
            });

            // Add pre-created attributes
            var attribute;
            var attributeName;
            var precreatedAttributes = model._precreatedAttributes;
            if (defined(precreatedAttributes)) {
                for (attributeName in precreatedAttributes) {
                    if (precreatedAttributes.hasOwnProperty(attributeName)) {
                        attributeLocation = attributeLocations[attributeName];
                        if (defined(attributeLocation)) {
                            attribute = precreatedAttributes[attributeName];
                            attribute.index = attributeLocation;
                            attributes.push(attribute);
                        }
                    }
                }
            }

            var indexBuffer;
            if (defined(primitive.indices)) {
                var accessor = accessors[primitive.indices];
                var bufferView = accessor.bufferView;

                // Use buffer of previously decoded draco geometry
                if (defined(decodedData)) {
                    bufferView = decodedData.bufferView;
                }

                indexBuffer = rendererBuffers[bufferView];
            }
            rendererVertexArrays[meshId + '.primitive.' + primitiveId] = new VertexArray({
                context: context,
                attributes: attributes,
                indexBuffer: indexBuffer
            });
        });
    });
}

function createRenderStates(model) {
    var loadResources = model._loadResources;
    if (loadResources.createRenderStates) {
        loadResources.createRenderStates = false;

        ForEach.material(model.gltf, function (material, materialId) {
            createRenderStateForMaterial(model, material, materialId);
        });
    }
}

function createRenderStateForMaterial(model, material, materialId) {
    var rendererRenderStates = model._rendererResources.renderStates;

    var blendEquationSeparate = [
        WebGLConstants.FUNC_ADD,
        WebGLConstants.FUNC_ADD
    ];
    var blendFuncSeparate = [
        WebGLConstants.ONE,
        WebGLConstants.ONE_MINUS_SRC_ALPHA,
        WebGLConstants.ONE,
        WebGLConstants.ONE_MINUS_SRC_ALPHA
    ];

    if (defined(material.extensions) && defined(material.extensions.KHR_blend)) {
        blendEquationSeparate = material.extensions.KHR_blend.blendEquation;
        blendFuncSeparate = material.extensions.KHR_blend.blendFactors;
    }

    var enableCulling = !material.doubleSided;
    var blendingEnabled = (material.alphaMode === 'BLEND');
    rendererRenderStates[materialId] = RenderState.fromCache({
        cull : {
            enabled : enableCulling
        },
        depthTest : {
            enabled : true,
            func: DepthFunction.LESS_OR_EQUAL
        },
        depthMask : !blendingEnabled,
        blending : {
            enabled : blendingEnabled,
            equationRgb : blendEquationSeparate[0],
            equationAlpha : blendEquationSeparate[1],
            functionSourceRgb : blendFuncSeparate[0],
            functionDestinationRgb : blendFuncSeparate[1],
            functionSourceAlpha : blendFuncSeparate[2],
            functionDestinationAlpha : blendFuncSeparate[3]
        }
    });
}

///////////////////////////////////////////////////////////////////////////

var gltfUniformsFromNode = {
    MODEL : function(uniformState, model, runtimeNode) {
        return function() {
            return runtimeNode.computedMatrix;
        };
    },
    VIEW : function(uniformState, model, runtimeNode) {
        return function() {
            return uniformState.view;
        };
    },
    PROJECTION : function(uniformState, model, runtimeNode) {
        return function() {
            return uniformState.projection;
        };
    },
    MODELVIEW : function(uniformState, model, runtimeNode) {
        var mv = new Matrix4();
        return function() {
            return Matrix4.multiplyTransformation(uniformState.view, runtimeNode.computedMatrix, mv);
        };
    },
    CESIUM_RTC_MODELVIEW : function(uniformState, model, runtimeNode) {
        // CESIUM_RTC extension
        var mvRtc = new Matrix4();
        return function() {
            Matrix4.multiplyTransformation(uniformState.view, runtimeNode.computedMatrix, mvRtc);
            return Matrix4.setTranslation(mvRtc, model._rtcCenterEye, mvRtc);
        };
    },
    MODELVIEWPROJECTION : function(uniformState, model, runtimeNode) {
        var mvp = new Matrix4();
        return function() {
            Matrix4.multiplyTransformation(uniformState.view, runtimeNode.computedMatrix, mvp);
            return Matrix4.multiply(uniformState._projection, mvp, mvp);
        };
    },
    MODELINVERSE : function(uniformState, model, runtimeNode) {
        var mInverse = new Matrix4();
        return function() {
            return Matrix4.inverse(runtimeNode.computedMatrix, mInverse);
        };
    },
    VIEWINVERSE : function(uniformState, model) {
        return function() {
            return uniformState.inverseView;
        };
    },
    PROJECTIONINVERSE : function(uniformState, model, runtimeNode) {
        return function() {
            return uniformState.inverseProjection;
        };
    },
    MODELVIEWINVERSE : function(uniformState, model, runtimeNode) {
        var mv = new Matrix4();
        var mvInverse = new Matrix4();
        return function() {
            Matrix4.multiplyTransformation(uniformState.view, runtimeNode.computedMatrix, mv);
            return Matrix4.inverse(mv, mvInverse);
        };
    },
    MODELVIEWPROJECTIONINVERSE : function(uniformState, model, runtimeNode) {
        var mvp = new Matrix4();
        var mvpInverse = new Matrix4();
        return function() {
            Matrix4.multiplyTransformation(uniformState.view, runtimeNode.computedMatrix, mvp);
            Matrix4.multiply(uniformState._projection, mvp, mvp);
            return Matrix4.inverse(mvp, mvpInverse);
        };
    },
    MODELINVERSETRANSPOSE : function(uniformState, model, runtimeNode) {
        var mInverse = new Matrix4();
        var mInverseTranspose = new Matrix3();
        return function() {
            Matrix4.inverse(runtimeNode.computedMatrix, mInverse);
            Matrix4.getMatrix3(mInverse, mInverseTranspose);
            return Matrix3.transpose(mInverseTranspose, mInverseTranspose);
        };
    },
    MODELVIEWINVERSETRANSPOSE : function(uniformState, model, runtimeNode) {
        var mv = new Matrix4();
        var mvInverse = new Matrix4();
        var mvInverseTranspose = new Matrix3();
        return function() {
            Matrix4.multiplyTransformation(uniformState.view, runtimeNode.computedMatrix, mv);
            Matrix4.inverse(mv, mvInverse);
            Matrix4.getMatrix3(mvInverse, mvInverseTranspose);
            return Matrix3.transpose(mvInverseTranspose, mvInverseTranspose);
        };
    },
    VIEWPORT : function(uniformState, model, runtimeNode) {
        return function() {
            return uniformState.viewportCartesian4;
        };
    }
};

function getUniformFunctionFromSource(source, model, semantic, uniformState) {
    var runtimeNode = model._runtime.nodes[source];
    return gltfUniformsFromNode[semantic](uniformState, model, runtimeNode);
}

function createUniformsForMaterial(model, material, technique, instanceValues, context, textures, defaultTexture) {
    var uniformMap = {};
    var uniformValues = {};
    var jointMatrixUniformName;
    var morphWeightsUniformName;

    ForEach.techniqueUniform(technique, function(uniform, uniformName) {
        // GLTF_SPEC: This does not take into account uniform arrays,
        // indicated by uniforms with a count property.
        //
        // https://github.com/KhronosGroup/glTF/issues/258

        // GLTF_SPEC: In this implementation, material parameters with a
        // semantic or targeted via a source (for animation) are not
        // targetable for material animations.  Is this too strict?
        //
        // https://github.com/KhronosGroup/glTF/issues/142

        var uv;
        if (defined(instanceValues) && defined(instanceValues[uniformName])) {
            // Parameter overrides by the instance technique
            uv = ModelUtility.createUniformFunction(uniform.type, instanceValues[uniformName], textures, defaultTexture);
            uniformMap[uniformName] = uv.func;
            uniformValues[uniformName] = uv;
        } else if (defined(uniform.node)) {
            uniformMap[uniformName] = getUniformFunctionFromSource(uniform.node, model, uniform.semantic, context.uniformState);
        } else if (defined(uniform.semantic)) {
            if (uniform.semantic === 'JOINTMATRIX') {
                jointMatrixUniformName = uniformName;
            } else if (uniform.semantic === 'MORPHWEIGHTS') {
                morphWeightsUniformName = uniformName;
            } else if (uniform.semantic === 'ALPHACUTOFF') {
                // The material's alphaCutoff value uses a uniform with semantic ALPHACUTOFF.
                // A uniform with this semantic will ignore the instance or default values.
                var alphaMode = material.alphaMode;
                if (defined(alphaMode) && alphaMode === 'MASK') {
                    var alphaCutoffValue = defaultValue(material.alphaCutoff, 0.5);
                    uv = ModelUtility.createUniformFunction(uniform.type, alphaCutoffValue, textures, defaultTexture);
                    uniformMap[uniformName] = uv.func;
                    uniformValues[uniformName] = uv;
                }
            } else {
                // Map glTF semantic to Cesium automatic uniform
                uniformMap[uniformName] = ModelUtility.getGltfSemanticUniforms()[uniform.semantic](context.uniformState, model);
            }
        } else if (defined(uniform.value)) {
            // Technique value that isn't overridden by a material
            var uv2 = ModelUtility.createUniformFunction(uniform.type, uniform.value, textures, defaultTexture);
            uniformMap[uniformName] = uv2.func;
            uniformValues[uniformName] = uv2;
        }
    });

    return {
        map : uniformMap,
        values : uniformValues,
        jointMatrixUniformName : jointMatrixUniformName,
        morphWeightsUniformName : morphWeightsUniformName
    };
}

function createUniformMaps(model, context) {
    var loadResources = model._loadResources;

    if (!loadResources.finishedProgramCreation()) {
        return;
    }

    if (!loadResources.createUniformMaps) {
        return;
    }
    loadResources.createUniformMaps = false;

    var gltf = model.gltf;
    var techniques = model._sourceTechniques;
    var uniformMaps = model._uniformMaps;

    var textures = model._rendererResources.textures;
    var defaultTexture = model._defaultTexture;

    ForEach.material(gltf, function (material, materialId) {
        var modelMaterial = model._runtime.materialsById[materialId];
        var technique = techniques[modelMaterial._technique];
        var instanceValues = modelMaterial._values;

        var uniforms = createUniformsForMaterial(model, material, technique, instanceValues, context, textures, defaultTexture);

        var u = uniformMaps[materialId];
        u.uniformMap = uniforms.map;                          // uniform name -> function for the renderer
        u.values = uniforms.values;                           // material parameter name -> ModelMaterial for modifying the parameter at runtime
        u.jointMatrixUniformName = uniforms.jointMatrixUniformName;
        u.morphWeightsUniformName = uniforms.morphWeightsUniformName;
    });
}

function createUniformsForDracoQuantizedAttributes(decodedData) {
    return ModelUtility.createUniformsForDracoQuantizedAttributes(decodedData.attributes);
}

function createUniformsForQuantizedAttributes(model, primitive) {
    var programId = getProgramForPrimitive(model, primitive);
    var quantizedUniforms = model._quantizedUniforms[programId];
    return ModelUtility.createUniformsForQuantizedAttributes(model.gltf, primitive, quantizedUniforms);
}

function createPickColorFunction(color) {
    return function() {
        return color;
    };
}

function createJointMatricesFunction(runtimeNode) {
    return function() {
        return runtimeNode.computedJointMatrices;
    };
}

function createMorphWeightsFunction(runtimeNode) {
    return function() {
        return runtimeNode.weights;
    };
}

function createSilhouetteColorFunction(model) {
    return function() {
        return model.silhouetteColor;
    };
}

function createSilhouetteSizeFunction(model) {
    return function() {
        return model.silhouetteSize;
    };
}

function createColorFunction(model) {
    return function() {
        return model.color;
    };
}

var scratchClippingPlaneMatrix = new Matrix4();
function createClippingPlanesMatrixFunction(model) {
    return function() {
        var clippingPlanes = model.clippingPlanes;
        if (!defined(clippingPlanes) && !defined(model._sphericalHarmonicCoefficients) && !defined(model._specularEnvironmentMaps)) {
            return Matrix4.IDENTITY;
        }
        var modelMatrix = defined(clippingPlanes) ? clippingPlanes.modelMatrix : Matrix4.IDENTITY;
        return Matrix4.multiply(model._clippingPlaneModelViewMatrix, modelMatrix, scratchClippingPlaneMatrix);
    };
}

function createClippingPlanesFunction(model) {
    return function() {
        var clippingPlanes = model.clippingPlanes;
        return (!defined(clippingPlanes) || !clippingPlanes.enabled) ? model._defaultTexture : clippingPlanes.texture;
    };
}

function createClippingPlanesEdgeStyleFunction(model) {
    return function() {
        var clippingPlanes = model.clippingPlanes;
        if (!defined(clippingPlanes)) {
            return Color.WHITE.withAlpha(0.0);
        }

        var style = Color.clone(clippingPlanes.edgeColor);
        style.alpha = clippingPlanes.edgeWidth;
        return style;
    };
}

function createColorBlendFunction(model) {
    return function() {
        return ColorBlendMode.getColorBlend(model.colorBlendMode, model.colorBlendAmount);
    };
}

function createIBLFactorFunction(model) {
    return function() {
        return model._imageBasedLightingFactor;
    };
}

function createLightColorFunction(model) {
    return function() {
        return model._lightColor;
    };
}

function createLuminanceAtZenithFunction(model) {
    return function() {
        return model.luminanceAtZenith;
    };
}

function createSphericalHarmonicCoefficientsFunction(model) {
    return function() {
        return model._sphericalHarmonicCoefficients;
    };
}

function createSpecularEnvironmentMapFunction(model) {
    return function() {
        return model._specularEnvironmentMapAtlas.texture;
    };
}

function createSpecularEnvironmentMapSizeFunction(model) {
    return function() {
        return model._specularEnvironmentMapAtlas.texture.dimensions;
    };
}

function createSpecularEnvironmentMapLOD(model) {
    return function() {
        return model._specularEnvironmentMapAtlas.maximumMipmapLevel;
    };
}

function triangleCountFromPrimitiveIndices(primitive, indicesCount) {
    switch (primitive.mode) {
        case PrimitiveType.TRIANGLES:
            return (indicesCount / 3);
        case PrimitiveType.TRIANGLE_STRIP:
        case PrimitiveType.TRIANGLE_FAN:
            return Math.max(indicesCount - 2, 0);
        default:
            return 0;
    }
}

function createCommand(model, gltfNode, runtimeNode, context, scene3DOnly) {
    var nodeCommands = model._nodeCommands;
    var pickIds = model._pickIds;
    var allowPicking = model.allowPicking;
    var runtimeMeshesByName = model._runtime.meshesByName;

    var resources = model._rendererResources;
    var rendererVertexArrays = resources.vertexArrays;
    var rendererPrograms = resources.programs;
    var rendererRenderStates = resources.renderStates;
    var uniformMaps = model._uniformMaps;

    var gltf = model.gltf;
    var accessors = gltf.accessors;
    var gltfMeshes = gltf.meshes;

    var id = gltfNode.mesh;
    var mesh = gltfMeshes[id];

    var primitives = mesh.primitives;
    var length = primitives.length;

    // The glTF node hierarchy is a DAG so a node can have more than one
    // parent, so a node may already have commands.  If so, append more
    // since they will have a different model matrix.

    for (var i = 0; i < length; ++i) {
        var primitive = primitives[i];
        var ix = accessors[primitive.indices];
        var material = model._runtime.materialsById[primitive.material];
        var programId = material._program;
        var decodedData = model._decodedData[id + '.primitive.' + i];

        var boundingSphere;
        var positionAccessor = primitive.attributes.POSITION;
        if (defined(positionAccessor)) {
            var minMax = ModelUtility.getAccessorMinMax(gltf, positionAccessor);
            boundingSphere = BoundingSphere.fromCornerPoints(Cartesian3.fromArray(minMax.min), Cartesian3.fromArray(minMax.max));
        }

        var vertexArray = rendererVertexArrays[id + '.primitive.' + i];
        var offset;
        var count;

        // Use indices of the previously decoded Draco geometry.
        if (defined(decodedData)) {
            count = decodedData.numberOfIndices;
            offset = 0;
        } else if (defined(ix)) {
            count = ix.count;
            offset = (ix.byteOffset / IndexDatatype.getSizeInBytes(ix.componentType)); // glTF has offset in bytes.  Cesium has offsets in indices
        } else {
            var positions = accessors[primitive.attributes.POSITION];
            count = positions.count;
            offset = 0;
        }

        // Update model triangle count using number of indices
        model._trianglesLength += triangleCountFromPrimitiveIndices(primitive, count);

        var um = uniformMaps[primitive.material];
        var uniformMap = um.uniformMap;
        if (defined(um.jointMatrixUniformName)) {
            var jointUniformMap = {};
            jointUniformMap[um.jointMatrixUniformName] = createJointMatricesFunction(runtimeNode);

            uniformMap = combine(uniformMap, jointUniformMap);
        }
        if (defined(um.morphWeightsUniformName)) {
            var morphWeightsUniformMap = {};
            morphWeightsUniformMap[um.morphWeightsUniformName] = createMorphWeightsFunction(runtimeNode);

            uniformMap = combine(uniformMap, morphWeightsUniformMap);
        }

        uniformMap = combine(uniformMap, {
            gltf_color : createColorFunction(model),
            gltf_colorBlend : createColorBlendFunction(model),
            gltf_clippingPlanes : createClippingPlanesFunction(model),
            gltf_clippingPlanesEdgeStyle : createClippingPlanesEdgeStyleFunction(model),
            gltf_clippingPlanesMatrix : createClippingPlanesMatrixFunction(model),
            gltf_iblFactor : createIBLFactorFunction(model),
            gltf_lightColor : createLightColorFunction(model),
            gltf_sphericalHarmonicCoefficients : createSphericalHarmonicCoefficientsFunction(model),
            gltf_specularMap : createSpecularEnvironmentMapFunction(model),
            gltf_specularMapSize : createSpecularEnvironmentMapSizeFunction(model),
            gltf_maxSpecularLOD : createSpecularEnvironmentMapLOD(model),
            gltf_luminanceAtZenith : createLuminanceAtZenithFunction(model)
        });

        // Allow callback to modify the uniformMap
        if (defined(model._uniformMapLoaded)) {
            uniformMap = model._uniformMapLoaded(uniformMap, programId, runtimeNode);
        }

        // Add uniforms for decoding quantized attributes if used
        var quantizedUniformMap = {};
        if (model.extensionsUsed.WEB3D_quantized_attributes) {
            quantizedUniformMap = createUniformsForQuantizedAttributes(model, primitive);
        } else if (model._dequantizeInShader && defined(decodedData)) {
            quantizedUniformMap = createUniformsForDracoQuantizedAttributes(decodedData);
        }
        uniformMap = combine(uniformMap, quantizedUniformMap);

        var rs = rendererRenderStates[primitive.material];
        var isTranslucent = rs.blending.enabled;

        var owner = model._pickObject;
        if (!defined(owner)) {
            owner = {
                primitive : model,
                id : model.id,
                node : runtimeNode.publicNode,
                mesh : runtimeMeshesByName[mesh.name]
            };
        }

        var castShadows = ShadowMode.castShadows(model._shadows);
        var receiveShadows = ShadowMode.receiveShadows(model._shadows);

        var pickId;
        if (allowPicking && !defined(model._uniformMapLoaded)) {
            pickId = context.createPickId(owner);
            pickIds.push(pickId);
            var pickUniforms = {
                czm_pickColor : createPickColorFunction(pickId.color)
            };
            uniformMap = combine(uniformMap, pickUniforms);
        }

        if (allowPicking) {
            if (defined(model._pickIdLoaded) && defined(model._uniformMapLoaded)) {
                pickId = model._pickIdLoaded();
            } else {
                pickId = 'czm_pickColor';
            }
        }

        var command = new DrawCommand({
            boundingVolume : new BoundingSphere(), // updated in update()
            cull : model.cull,
            modelMatrix : new Matrix4(),           // computed in update()
            primitiveType : primitive.mode,
            vertexArray : vertexArray,
            count : count,
            offset : offset,
            shaderProgram : rendererPrograms[programId],
            castShadows : castShadows,
            receiveShadows : receiveShadows,
            uniformMap : uniformMap,
            renderState : rs,
            owner : owner,
            pass : isTranslucent ? Pass.TRANSLUCENT : model.opaquePass,
            pickId : pickId
        });

        var command2D;
        if (!scene3DOnly) {
            command2D = DrawCommand.shallowClone(command);
            command2D.boundingVolume = new BoundingSphere(); // updated in update()
            command2D.modelMatrix = new Matrix4();           // updated in update()
        }

        var nodeCommand = {
            show : true,
            boundingSphere : boundingSphere,
            command : command,
            command2D : command2D,
            // Generated on demand when silhouette size is greater than 0.0 and silhouette alpha is greater than 0.0
            silhouetteModelCommand : undefined,
            silhouetteModelCommand2D : undefined,
            silhouetteColorCommand : undefined,
            silhouetteColorCommand2D : undefined,
            // Generated on demand when color alpha is less than 1.0
            translucentCommand : undefined,
            translucentCommand2D : undefined,
            // For updating node commands on shader reconstruction
            programId : programId
        };
        runtimeNode.commands.push(nodeCommand);
        nodeCommands.push(nodeCommand);
    }
}

function createRuntimeNodes(model, context, scene3DOnly) {
    var loadResources = model._loadResources;

    if (!loadResources.finishedEverythingButTextureCreation()) {
        return;
    }

    if (!loadResources.createRuntimeNodes) {
        return;
    }
    loadResources.createRuntimeNodes = false;

    var rootNodes = [];
    var runtimeNodes = model._runtime.nodes;

    var gltf = model.gltf;
    var nodes = gltf.nodes;
    var skins = gltf.skins;

    var scene = gltf.scenes[gltf.scene];
    var sceneNodes = scene.nodes;
    var length = sceneNodes.length;

    var stack = [];
    var seen = {};

    for (var i = 0; i < length; ++i) {
        stack.push({
            parentRuntimeNode : undefined,
            gltfNode : nodes[sceneNodes[i]],
            id : sceneNodes[i]
        });

        var skeletonIds = [];
        while (stack.length > 0) {
            var n = stack.pop();
            seen[n.id] = true;
            var parentRuntimeNode = n.parentRuntimeNode;
            var gltfNode = n.gltfNode;

            // Node hierarchy is a DAG so a node can have more than one parent so it may already exist
            var runtimeNode = runtimeNodes[n.id];
            if (runtimeNode.parents.length === 0) {
                if (defined(gltfNode.matrix)) {
                    runtimeNode.matrix = Matrix4.fromColumnMajorArray(gltfNode.matrix);
                } else {
                    // TRS converted to Cesium types
                    var rotation = gltfNode.rotation;
                    runtimeNode.translation = Cartesian3.fromArray(gltfNode.translation);
                    runtimeNode.rotation = Quaternion.unpack(rotation);
                    runtimeNode.scale = Cartesian3.fromArray(gltfNode.scale);
                }
            }

            if (defined(parentRuntimeNode)) {
                parentRuntimeNode.children.push(runtimeNode);
                runtimeNode.parents.push(parentRuntimeNode);
            } else {
                rootNodes.push(runtimeNode);
            }

            if (defined(gltfNode.mesh)) {
                createCommand(model, gltfNode, runtimeNode, context, scene3DOnly);
            }

            var children = gltfNode.children;
            if (defined(children)) {
                var childrenLength = children.length;
                for (var j = 0; j < childrenLength; j++) {
                    var childId = children[j];
                    if (!seen[childId]) {
                        stack.push({
                            parentRuntimeNode : runtimeNode,
                            gltfNode : nodes[childId],
                            id : children[j]
                        });
                    }
                }
            }

            var skin = gltfNode.skin;
            if (defined(skin)) {
                skeletonIds.push(skins[skin].skeleton);
            }

            if (stack.length === 0) {
                for (var k = 0; k < skeletonIds.length; k++) {
                    var skeleton = skeletonIds[k];
                    if (!seen[skeleton]) {
                        stack.push({
                            parentRuntimeNode : undefined,
                            gltfNode : nodes[skeleton],
                            id : skeleton
                        });
                    }
                }
            }
        }
    }

    model._runtime.rootNodes = rootNodes;
    model._runtime.nodes = runtimeNodes;
}

function getGeometryByteLength(buffers) {
    var memory = 0;
    for (var id in buffers) {
        if (buffers.hasOwnProperty(id)) {
            memory += buffers[id].sizeInBytes;
        }
    }
    return memory;
}

function getTexturesByteLength(textures) {
    var memory = 0;
    for (var id in textures) {
        if (textures.hasOwnProperty(id)) {
            memory += textures[id].sizeInBytes;
        }
    }
    return memory;
}

function createResources(model, frameState) {
    var context = frameState.context;
    var scene3DOnly = frameState.scene3DOnly;
    var quantizedVertexShaders = model._quantizedVertexShaders;
    var toClipCoordinates = model._toClipCoordinatesGLSL = {};
    var techniques = model._sourceTechniques;
    var programs = model._sourcePrograms;

    var resources = model._rendererResources;
    var shaders = resources.sourceShaders;
    if (model._loadRendererResourcesFromCache) {
        shaders = resources.sourceShaders = model._cachedRendererResources.sourceShaders;
    }

    for (var techniqueId in techniques) {
        if (techniques.hasOwnProperty(techniqueId)) {
            var programId = techniques[techniqueId].program;
            var program = programs[programId];
            var shader = shaders[program.vertexShader];

            ModelUtility.checkSupportedGlExtensions(program.glExtensions, context);

            if (model.extensionsUsed.WEB3D_quantized_attributes || model._dequantizeInShader) {
                var quantizedVS = quantizedVertexShaders[programId];
                if (!defined(quantizedVS)) {
                    quantizedVS = modifyShaderForQuantizedAttributes(shader, programId, model);
                    quantizedVertexShaders[programId] = quantizedVS;
                }
                shader = quantizedVS;
            }

            shader = modifyShader(shader, programId, model._vertexShaderLoaded);
            toClipCoordinates[programId] = ModelUtility.toClipCoordinatesGLSL(model.gltf, shader);
        }
    }

    if (model._loadRendererResourcesFromCache) {
        var cachedResources = model._cachedRendererResources;

        resources.buffers = cachedResources.buffers;
        resources.vertexArrays = cachedResources.vertexArrays;
        resources.programs = cachedResources.programs;
        resources.silhouettePrograms = cachedResources.silhouettePrograms;
        resources.textures = cachedResources.textures;
        resources.samplers = cachedResources.samplers;
        resources.renderStates = cachedResources.renderStates;

        // Vertex arrays are unique to this model, create instead of using the cache.
        if (defined(model._precreatedAttributes)) {
            createVertexArrays(model, context);
        }

        model._cachedGeometryByteLength += getGeometryByteLength(cachedResources.buffers);
        model._cachedTexturesByteLength += getTexturesByteLength(cachedResources.textures);
    } else {
        createBuffers(model, frameState); // using glTF bufferViews
        createPrograms(model, frameState);
        createSamplers(model, context);
        loadTexturesFromBufferViews(model);
        createTextures(model, frameState);
    }

    createSkins(model);
    createRuntimeAnimations(model);

    if (!model._loadRendererResourcesFromCache) {
        createVertexArrays(model, context); // using glTF meshes
        createRenderStates(model); // using glTF materials/techniques/states
        // Long-term, we might not cache render states if they could change
        // due to an animation, e.g., a uniform going from opaque to transparent.
        // Could use copy-on-write if it is worth it.  Probably overkill.
    }

    createUniformMaps(model, context);               // using glTF materials/techniques
    createRuntimeNodes(model, context, scene3DOnly); // using glTF scene
}

///////////////////////////////////////////////////////////////////////////

function getNodeMatrix(node, result) {
    var publicNode = node.publicNode;
    var publicMatrix = publicNode.matrix;

    if (publicNode.useMatrix && defined(publicMatrix)) {
        // Public matrix overrides original glTF matrix and glTF animations
        Matrix4.clone(publicMatrix, result);
    } else if (defined(node.matrix)) {
        Matrix4.clone(node.matrix, result);
    } else {
        Matrix4.fromTranslationQuaternionRotationScale(node.translation, node.rotation, node.scale, result);
        // Keep matrix returned by the node in-sync if the node is targeted by an animation.  Only TRS nodes can be targeted.
        publicNode.setMatrix(result);
    }
}

var scratchNodeStack = [];
var scratchComputedTranslation = new Cartesian4();
var scratchComputedMatrixIn2D = new Matrix4();

function updateNodeHierarchyModelMatrix(model, modelTransformChanged, justLoaded, projection) {
    var maxDirtyNumber = model._maxDirtyNumber;

    var rootNodes = model._runtime.rootNodes;
    var length = rootNodes.length;

    var nodeStack = scratchNodeStack;
    var computedModelMatrix = model._computedModelMatrix;

    if ((model._mode !== SceneMode.SCENE3D) && !model._ignoreCommands) {
        var translation = Matrix4.getColumn(computedModelMatrix, 3, scratchComputedTranslation);
        if (!Cartesian4.equals(translation, Cartesian4.UNIT_W)) {
            computedModelMatrix = Transforms.basisTo2D(projection, computedModelMatrix, scratchComputedMatrixIn2D);
            model._rtcCenter = model._rtcCenter3D;
        } else {
            var center = model.boundingSphere.center;
            var to2D = Transforms.wgs84To2DModelMatrix(projection, center, scratchComputedMatrixIn2D);
            computedModelMatrix = Matrix4.multiply(to2D, computedModelMatrix, scratchComputedMatrixIn2D);

            if (defined(model._rtcCenter)) {
                Matrix4.setTranslation(computedModelMatrix, Cartesian4.UNIT_W, computedModelMatrix);
                model._rtcCenter = model._rtcCenter2D;
            }
        }
    }

    for (var i = 0; i < length; ++i) {
        var n = rootNodes[i];

        getNodeMatrix(n, n.transformToRoot);
        nodeStack.push(n);

        while (nodeStack.length > 0) {
            n = nodeStack.pop();
            var transformToRoot = n.transformToRoot;
            var commands = n.commands;

            if ((n.dirtyNumber === maxDirtyNumber) || modelTransformChanged || justLoaded) {
                var nodeMatrix = Matrix4.multiplyTransformation(computedModelMatrix, transformToRoot, n.computedMatrix);
                var commandsLength = commands.length;
                if (commandsLength > 0) {
                    // Node has meshes, which has primitives.  Update their commands.
                    for (var j = 0; j < commandsLength; ++j) {
                        var primitiveCommand = commands[j];
                        var command = primitiveCommand.command;
                        Matrix4.clone(nodeMatrix, command.modelMatrix);

                        // PERFORMANCE_IDEA: Can use transformWithoutScale if no node up to the root has scale (including animation)
                        BoundingSphere.transform(primitiveCommand.boundingSphere, command.modelMatrix, command.boundingVolume);

                        if (defined(model._rtcCenter)) {
                            Cartesian3.add(model._rtcCenter, command.boundingVolume.center, command.boundingVolume.center);
                        }

                        // If the model crosses the IDL in 2D, it will be drawn in one viewport, but part of it
                        // will be clipped by the viewport. We create a second command that translates the model
                        // model matrix to the opposite side of the map so the part that was clipped in one viewport
                        // is drawn in the other.
                        command = primitiveCommand.command2D;
                        if (defined(command) && model._mode === SceneMode.SCENE2D) {
                            Matrix4.clone(nodeMatrix, command.modelMatrix);
                            command.modelMatrix[13] -= CesiumMath.sign(command.modelMatrix[13]) * 2.0 * CesiumMath.PI * projection.ellipsoid.maximumRadius;
                            BoundingSphere.transform(primitiveCommand.boundingSphere, command.modelMatrix, command.boundingVolume);
                        }
                    }
                }
            }

            var children = n.children;
            if (defined(children)) {
                var childrenLength = children.length;
                for (var k = 0; k < childrenLength; ++k) {
                    var child = children[k];

                    // A node's transform needs to be updated if
                    // - It was targeted for animation this frame, or
                    // - Any of its ancestors were targeted for animation this frame

                    // PERFORMANCE_IDEA: if a child has multiple parents and only one of the parents
                    // is dirty, all the subtrees for each child instance will be dirty; we probably
                    // won't see this in the wild often.
                    child.dirtyNumber = Math.max(child.dirtyNumber, n.dirtyNumber);

                    if ((child.dirtyNumber === maxDirtyNumber) || justLoaded) {
                        // Don't check for modelTransformChanged since if only the model's model matrix changed,
                        // we do not need to rebuild the local transform-to-root, only the final
                        // [model's-model-matrix][transform-to-root] above.
                        getNodeMatrix(child, child.transformToRoot);
                        Matrix4.multiplyTransformation(transformToRoot, child.transformToRoot, child.transformToRoot);
                    }

                    nodeStack.push(child);
                }
            }
        }
    }

    ++model._maxDirtyNumber;
}

var scratchObjectSpace = new Matrix4();

function applySkins(model) {
    var skinnedNodes = model._runtime.skinnedNodes;
    var length = skinnedNodes.length;

    for (var i = 0; i < length; ++i) {
        var node = skinnedNodes[i];

        scratchObjectSpace = Matrix4.inverseTransformation(node.transformToRoot, scratchObjectSpace);

        var computedJointMatrices = node.computedJointMatrices;
        var joints = node.joints;
        var bindShapeMatrix = node.bindShapeMatrix;
        var inverseBindMatrices = node.inverseBindMatrices;
        var inverseBindMatricesLength = inverseBindMatrices.length;

        for (var m = 0; m < inverseBindMatricesLength; ++m) {
            // [joint-matrix] = [node-to-root^-1][joint-to-root][inverse-bind][bind-shape]
            if (!defined(computedJointMatrices[m])) {
                computedJointMatrices[m] = new Matrix4();
            }
            computedJointMatrices[m] = Matrix4.multiplyTransformation(scratchObjectSpace, joints[m].transformToRoot, computedJointMatrices[m]);
            computedJointMatrices[m] = Matrix4.multiplyTransformation(computedJointMatrices[m], inverseBindMatrices[m], computedJointMatrices[m]);
            if (defined(bindShapeMatrix)) {
                // Optimization for when bind shape matrix is the identity.
                computedJointMatrices[m] = Matrix4.multiplyTransformation(computedJointMatrices[m], bindShapeMatrix, computedJointMatrices[m]);
            }
        }
    }
}

function updatePerNodeShow(model) {
    // Totally not worth it, but we could optimize this:
    // http://help.agi.com/AGIComponents/html/BlogDeletionInBoundingVolumeHierarchies.htm

    var rootNodes = model._runtime.rootNodes;
    var length = rootNodes.length;

    var nodeStack = scratchNodeStack;

    for (var i = 0; i < length; ++i) {
        var n = rootNodes[i];
        n.computedShow = n.publicNode.show;
        nodeStack.push(n);

        while (nodeStack.length > 0) {
            n = nodeStack.pop();
            var show = n.computedShow;

            var nodeCommands = n.commands;
            var nodeCommandsLength = nodeCommands.length;
            for (var j = 0; j < nodeCommandsLength; ++j) {
                nodeCommands[j].show = show;
            }
            // if commandsLength is zero, the node has a light or camera

            var children = n.children;
            if (defined(children)) {
                var childrenLength = children.length;
                for (var k = 0; k < childrenLength; ++k) {
                    var child = children[k];
                    // Parent needs to be shown for child to be shown.
                    child.computedShow = show && child.publicNode.show;
                    nodeStack.push(child);
                }
            }
        }
    }
}

function updatePickIds(model, context) {
    var id = model.id;
    if (model._id !== id) {
        model._id = id;

        var pickIds = model._pickIds;
        var length = pickIds.length;
        for (var i = 0; i < length; ++i) {
            pickIds[i].object.id = id;
        }
    }
}

function updateWireframe(model) {
    if (model._debugWireframe !== model.debugWireframe) {
        model._debugWireframe = model.debugWireframe;

        // This assumes the original primitive was TRIANGLES and that the triangles
        // are connected for the wireframe to look perfect.
        var primitiveType = model.debugWireframe ? PrimitiveType.LINES : PrimitiveType.TRIANGLES;
        var nodeCommands = model._nodeCommands;
        var length = nodeCommands.length;

        for (var i = 0; i < length; ++i) {
            nodeCommands[i].command.primitiveType = primitiveType;
        }
    }
}

function updateShowBoundingVolume(model) {
    if (model.debugShowBoundingVolume !== model._debugShowBoundingVolume) {
        model._debugShowBoundingVolume = model.debugShowBoundingVolume;

        var debugShowBoundingVolume = model.debugShowBoundingVolume;
        var nodeCommands = model._nodeCommands;
        var length = nodeCommands.length;

        for (var i = 0; i < length; ++i) {
            nodeCommands[i].command.debugShowBoundingVolume = debugShowBoundingVolume;
        }
    }
}

function updateShadows(model) {
    if (model.shadows !== model._shadows) {
        model._shadows = model.shadows;

        var castShadows = ShadowMode.castShadows(model.shadows);
        var receiveShadows = ShadowMode.receiveShadows(model.shadows);
        var nodeCommands = model._nodeCommands;
        var length = nodeCommands.length;

        for (var i = 0; i < length; i++) {
            var nodeCommand = nodeCommands[i];
            nodeCommand.command.castShadows = castShadows;
            nodeCommand.command.receiveShadows = receiveShadows;
        }
    }
}

function getTranslucentRenderState(renderState) {
    var rs = clone(renderState, true);
    rs.cull.enabled = false;
    rs.depthTest.enabled = true;
    rs.depthMask = false;
    rs.blending = BlendingState.ALPHA_BLEND;

    return RenderState.fromCache(rs);
}

function deriveTranslucentCommand(command) {
    var translucentCommand = DrawCommand.shallowClone(command);
    translucentCommand.pass = Pass.TRANSLUCENT;
    translucentCommand.renderState = getTranslucentRenderState(command.renderState);
    return translucentCommand;
}

function updateColor(model, frameState, forceDerive) {
    // Generate translucent commands when the blend color has an alpha in the range (0.0, 1.0) exclusive
    var scene3DOnly = frameState.scene3DOnly;
    var alpha = model.color.alpha;
    if ((alpha > 0.0) && (alpha < 1.0)) {
        var nodeCommands = model._nodeCommands;
        var length = nodeCommands.length;
        if (!defined(nodeCommands[0].translucentCommand) || forceDerive) {
            for (var i = 0; i < length; ++i) {
                var nodeCommand = nodeCommands[i];
                var command = nodeCommand.command;
                nodeCommand.translucentCommand = deriveTranslucentCommand(command);
                if (!scene3DOnly) {
                    var command2D = nodeCommand.command2D;
                    nodeCommand.translucentCommand2D = deriveTranslucentCommand(command2D);
                }
            }
        }
    }
}

function getProgramId(model, program) {
    var programs = model._rendererResources.programs;
    for (var id in programs) {
        if (programs.hasOwnProperty(id)) {
            if (programs[id] === program) {
                return id;
            }
        }
    }
}

function createSilhouetteProgram(model, program, frameState) {
    var vs = program.vertexShaderSource.sources[0];
    var attributeLocations = program._attributeLocations;
    var normalAttributeName = model._normalAttributeName;

    // Modified from http://forum.unity3d.com/threads/toon-outline-but-with-diffuse-surface.24668/
    vs = ShaderSource.replaceMain(vs, 'gltf_silhouette_main');
    vs +=
        'uniform float gltf_silhouetteSize; \n' +
        'void main() \n' +
        '{ \n' +
        '    gltf_silhouette_main(); \n' +
        '    vec3 n = normalize(czm_normal3D * ' + normalAttributeName + '); \n' +
        '    n.x *= czm_projection[0][0]; \n' +
        '    n.y *= czm_projection[1][1]; \n' +
        '    vec4 clip = gl_Position; \n' +
        '    clip.xy += n.xy * clip.w * gltf_silhouetteSize * czm_pixelRatio / czm_viewport.z; \n' +
        '    gl_Position = clip; \n' +
        '}';

    var fs =
        'uniform vec4 gltf_silhouetteColor; \n' +
        'void main() \n' +
        '{ \n' +
        '    gl_FragColor = czm_gammaCorrect(gltf_silhouetteColor); \n' +
        '}';

    return ShaderProgram.fromCache({
        context : frameState.context,
        vertexShaderSource : vs,
        fragmentShaderSource : fs,
        attributeLocations : attributeLocations
    });
}

function hasSilhouette(model, frameState) {
    return silhouetteSupported(frameState.context) && (model.silhouetteSize > 0.0) && (model.silhouetteColor.alpha > 0.0) && defined(model._normalAttributeName);
}

function hasTranslucentCommands(model) {
    var nodeCommands = model._nodeCommands;
    var length = nodeCommands.length;
    for (var i = 0; i < length; ++i) {
        var nodeCommand = nodeCommands[i];
        var command = nodeCommand.command;
        if (command.pass === Pass.TRANSLUCENT) {
            return true;
        }
    }
    return false;
}

function isTranslucent(model) {
    return (model.color.alpha > 0.0) && (model.color.alpha < 1.0);
}

function isInvisible(model) {
    return (model.color.alpha === 0.0);
}

function alphaDirty(currAlpha, prevAlpha) {
    // Returns whether the alpha state has changed between invisible, translucent, or opaque
    return (Math.floor(currAlpha) !== Math.floor(prevAlpha)) || (Math.ceil(currAlpha) !== Math.ceil(prevAlpha));
}

var silhouettesLength = 0;

function createSilhouetteCommands(model, frameState) {
    // Wrap around after exceeding the 8-bit stencil limit.
    // The reference is unique to each model until this point.
    var stencilReference = (++silhouettesLength) % 255;

    // If the model is translucent the silhouette needs to be in the translucent pass.
    // Otherwise the silhouette would be rendered before the model.
    var silhouetteTranslucent = hasTranslucentCommands(model) || isTranslucent(model) || (model.silhouetteColor.alpha < 1.0);
    var silhouettePrograms = model._rendererResources.silhouettePrograms;
    var scene3DOnly = frameState.scene3DOnly;
    var nodeCommands = model._nodeCommands;
    var length = nodeCommands.length;
    for (var i = 0; i < length; ++i) {
        var nodeCommand = nodeCommands[i];
        var command = nodeCommand.command;

        // Create model command
        var modelCommand = isTranslucent(model) ? nodeCommand.translucentCommand : command;
        var silhouetteModelCommand = DrawCommand.shallowClone(modelCommand);
        var renderState = clone(modelCommand.renderState);

        // Write the reference value into the stencil buffer.
        renderState.stencilTest = {
            enabled : true,
            frontFunction : WebGLConstants.ALWAYS,
            backFunction : WebGLConstants.ALWAYS,
            reference : stencilReference,
            mask : ~0,
            frontOperation : {
                fail : WebGLConstants.KEEP,
                zFail : WebGLConstants.KEEP,
                zPass : WebGLConstants.REPLACE
            },
            backOperation : {
                fail : WebGLConstants.KEEP,
                zFail : WebGLConstants.KEEP,
                zPass : WebGLConstants.REPLACE
            }
        };

        if (isInvisible(model)) {
            // When the model is invisible disable color and depth writes but still write into the stencil buffer
            renderState.colorMask = {
                red : false,
                green : false,
                blue : false,
                alpha : false
            };
            renderState.depthMask = false;
        }
        renderState = RenderState.fromCache(renderState);
        silhouetteModelCommand.renderState = renderState;
        nodeCommand.silhouetteModelCommand = silhouetteModelCommand;

        // Create color command
        var silhouetteColorCommand = DrawCommand.shallowClone(command);
        renderState = clone(command.renderState, true);
        renderState.depthTest.enabled = true;
        renderState.cull.enabled = false;
        if (silhouetteTranslucent) {
            silhouetteColorCommand.pass = Pass.TRANSLUCENT;
            renderState.depthMask = false;
            renderState.blending = BlendingState.ALPHA_BLEND;
        }

        // Only render silhouette if the value in the stencil buffer equals the reference
        renderState.stencilTest = {
            enabled : true,
            frontFunction : WebGLConstants.NOTEQUAL,
            backFunction : WebGLConstants.NOTEQUAL,
            reference : stencilReference,
            mask : ~0,
            frontOperation : {
                fail : WebGLConstants.KEEP,
                zFail : WebGLConstants.KEEP,
                zPass : WebGLConstants.KEEP
            },
            backOperation : {
                fail : WebGLConstants.KEEP,
                zFail : WebGLConstants.KEEP,
                zPass : WebGLConstants.KEEP
            }
        };
        renderState = RenderState.fromCache(renderState);

        // If the silhouette program has already been cached use it
        var program = command.shaderProgram;
        var id = getProgramId(model, program);
        var silhouetteProgram = silhouettePrograms[id];
        if (!defined(silhouetteProgram)) {
            silhouetteProgram = createSilhouetteProgram(model, program, frameState);
            silhouettePrograms[id] = silhouetteProgram;
        }

        var silhouetteUniformMap = combine(command.uniformMap, {
            gltf_silhouetteColor : createSilhouetteColorFunction(model),
            gltf_silhouetteSize : createSilhouetteSizeFunction(model)
        });

        silhouetteColorCommand.renderState = renderState;
        silhouetteColorCommand.shaderProgram = silhouetteProgram;
        silhouetteColorCommand.uniformMap = silhouetteUniformMap;
        silhouetteColorCommand.castShadows = false;
        silhouetteColorCommand.receiveShadows = false;
        nodeCommand.silhouetteColorCommand = silhouetteColorCommand;

        if (!scene3DOnly) {
            var command2D = nodeCommand.command2D;
            var silhouetteModelCommand2D = DrawCommand.shallowClone(silhouetteModelCommand);
            silhouetteModelCommand2D.boundingVolume = command2D.boundingVolume;
            silhouetteModelCommand2D.modelMatrix = command2D.modelMatrix;
            nodeCommand.silhouetteModelCommand2D = silhouetteModelCommand2D;

            var silhouetteColorCommand2D = DrawCommand.shallowClone(silhouetteColorCommand);
            silhouetteModelCommand2D.boundingVolume = command2D.boundingVolume;
            silhouetteModelCommand2D.modelMatrix = command2D.modelMatrix;
            nodeCommand.silhouetteColorCommand2D = silhouetteColorCommand2D;
        }
    }
}

function modifyShaderForClippingPlanes(shader, clippingPlaneCollection, context) {
    shader = ShaderSource.replaceMain(shader, 'gltf_clip_main');
    shader += Model._getClippingFunction(clippingPlaneCollection, context) + '\n';
    shader +=
        'uniform sampler2D gltf_clippingPlanes; \n' +
        'uniform mat4 gltf_clippingPlanesMatrix; \n' +
        'uniform vec4 gltf_clippingPlanesEdgeStyle; \n' +
        'void main() \n' +
        '{ \n' +
        '    gltf_clip_main(); \n' +
        getClipAndStyleCode('gltf_clippingPlanes', 'gltf_clippingPlanesMatrix', 'gltf_clippingPlanesEdgeStyle') +
        '} \n';
    return shader;
}

function updateSilhouette(model, frameState, force) {
    // Generate silhouette commands when the silhouette size is greater than 0.0 and the alpha is greater than 0.0
    // There are two silhouette commands:
    //     1. silhouetteModelCommand : render model normally while enabling stencil mask
    //     2. silhouetteColorCommand : render enlarged model with a solid color while enabling stencil tests
    if (!hasSilhouette(model, frameState)) {
        return;
    }

    var nodeCommands = model._nodeCommands;
    var dirty = alphaDirty(model.color.alpha, model._colorPreviousAlpha) ||
        alphaDirty(model.silhouetteColor.alpha, model._silhouetteColorPreviousAlpha) ||
        !defined(nodeCommands[0].silhouetteModelCommand);

    model._colorPreviousAlpha = model.color.alpha;
    model._silhouetteColorPreviousAlpha = model.silhouetteColor.alpha;

    if (dirty || force) {
        createSilhouetteCommands(model, frameState);
    }
}

function updateClippingPlanes(model, frameState) {
    var clippingPlanes = model._clippingPlanes;
    if (defined(clippingPlanes) && clippingPlanes.owner === model) {
        if (clippingPlanes.enabled) {
            clippingPlanes.update(frameState);
        }
    }
}

var scratchBoundingSphere = new BoundingSphere();

function scaleInPixels(positionWC, radius, frameState) {
    scratchBoundingSphere.center = positionWC;
    scratchBoundingSphere.radius = radius;
    return frameState.camera.getPixelSize(scratchBoundingSphere, frameState.context.drawingBufferWidth, frameState.context.drawingBufferHeight);
}

var scratchPosition = new Cartesian3();
var scratchCartographic = new Cartographic();

function getScale(model, frameState) {
    var scale = model.scale;

    if (model.minimumPixelSize !== 0.0) {
        // Compute size of bounding sphere in pixels
        var context = frameState.context;
        var maxPixelSize = Math.max(context.drawingBufferWidth, context.drawingBufferHeight);
        var m = defined(model._clampedModelMatrix) ? model._clampedModelMatrix : model.modelMatrix;
        scratchPosition.x = m[12];
        scratchPosition.y = m[13];
        scratchPosition.z = m[14];

        if (defined(model._rtcCenter)) {
            Cartesian3.add(model._rtcCenter, scratchPosition, scratchPosition);
        }

        if (model._mode !== SceneMode.SCENE3D) {
            var projection = frameState.mapProjection;
            var cartographic = projection.ellipsoid.cartesianToCartographic(scratchPosition, scratchCartographic);
            projection.project(cartographic, scratchPosition);
            Cartesian3.fromElements(scratchPosition.z, scratchPosition.x, scratchPosition.y, scratchPosition);
        }

        var radius = model.boundingSphere.radius;
        var metersPerPixel = scaleInPixels(scratchPosition, radius, frameState);

        // metersPerPixel is always > 0.0
        var pixelsPerMeter = 1.0 / metersPerPixel;
        var diameterInPixels = Math.min(pixelsPerMeter * (2.0 * radius), maxPixelSize);

        // Maintain model's minimum pixel size
        if (diameterInPixels < model.minimumPixelSize) {
            scale = (model.minimumPixelSize * metersPerPixel) / (2.0 * model._initialRadius);
        }
    }

    return defined(model.maximumScale) ? Math.min(model.maximumScale, scale) : scale;
}

function releaseCachedGltf(model) {
    if (defined(model._cacheKey) && defined(model._cachedGltf) && (--model._cachedGltf.count === 0)) {
        delete gltfCache[model._cacheKey];
    }
    model._cachedGltf = undefined;
}

///////////////////////////////////////////////////////////////////////////

function CachedRendererResources(context, cacheKey) {
    this.buffers = undefined;
    this.vertexArrays = undefined;
    this.programs = undefined;
    this.sourceShaders = undefined;
    this.silhouettePrograms = undefined;
    this.textures = undefined;
    this.samplers = undefined;
    this.renderStates = undefined;
    this.ready = false;

    this.context = context;
    this.cacheKey = cacheKey;
    this.count = 0;
}

function destroy(property) {
    for (var name in property) {
        if (property.hasOwnProperty(name)) {
            property[name].destroy();
        }
    }
}

function destroyCachedRendererResources(resources) {
    destroy(resources.buffers);
    destroy(resources.vertexArrays);
    destroy(resources.programs);
    destroy(resources.silhouettePrograms);
    destroy(resources.textures);
}

CachedRendererResources.prototype.release = function() {
    if (--this.count === 0) {
        if (defined(this.cacheKey)) {
            // Remove if this was cached
            delete this.context.cache.modelRendererResourceCache[this.cacheKey];
        }
        destroyCachedRendererResources(this);
        return destroyObject(this);
    }

    return undefined;
};

///////////////////////////////////////////////////////////////////////////

function getUpdateHeightCallback(model, ellipsoid, cartoPosition) {
    return function(clampedPosition) {
        if (model.heightReference === HeightReference.RELATIVE_TO_GROUND) {
            var clampedCart = ellipsoid.cartesianToCartographic(clampedPosition, scratchCartographic);
            clampedCart.height += cartoPosition.height;
            ellipsoid.cartographicToCartesian(clampedCart, clampedPosition);
        }

        var clampedModelMatrix = model._clampedModelMatrix;

        // Modify clamped model matrix to use new height
        Matrix4.clone(model.modelMatrix, clampedModelMatrix);
        clampedModelMatrix[12] = clampedPosition.x;
        clampedModelMatrix[13] = clampedPosition.y;
        clampedModelMatrix[14] = clampedPosition.z;

        model._heightChanged = true;
    };
}

function updateClamping(model) {
    if (defined(model._removeUpdateHeightCallback)) {
        model._removeUpdateHeightCallback();
        model._removeUpdateHeightCallback = undefined;
    }

    var scene = model._scene;
    if (!defined(scene) || !defined(scene.globe) || (model.heightReference === HeightReference.NONE)) {
        //>>includeStart('debug', pragmas.debug);
        if (model.heightReference !== HeightReference.NONE) {
            throw new DeveloperError('Height reference is not supported without a scene and globe.');
        }
        //>>includeEnd('debug');
        model._clampedModelMatrix = undefined;
        return;
    }

    var globe = scene.globe;
    var ellipsoid = globe.ellipsoid;

    // Compute cartographic position so we don't recompute every update
    var modelMatrix = model.modelMatrix;
    scratchPosition.x = modelMatrix[12];
    scratchPosition.y = modelMatrix[13];
    scratchPosition.z = modelMatrix[14];
    var cartoPosition = ellipsoid.cartesianToCartographic(scratchPosition);

    if (!defined(model._clampedModelMatrix)) {
        model._clampedModelMatrix = Matrix4.clone(modelMatrix, new Matrix4());
    }

    // Install callback to handle updating of terrain tiles
    var surface = globe._surface;
    model._removeUpdateHeightCallback = surface.updateHeight(cartoPosition, getUpdateHeightCallback(model, ellipsoid, cartoPosition));

    // Set the correct height now
    var height = globe.getHeight(cartoPosition);
    if (defined(height)) {
        // Get callback with cartoPosition being the non-clamped position
        var cb = getUpdateHeightCallback(model, ellipsoid, cartoPosition);

        // Compute the clamped cartesian and call updateHeight callback
        Cartographic.clone(cartoPosition, scratchCartographic);
        scratchCartographic.height = height;
        ellipsoid.cartographicToCartesian(scratchCartographic, scratchPosition);
        cb(scratchPosition);
    }
}

var scratchDisplayConditionCartesian = new Cartesian3();
var scratchDistanceDisplayConditionCartographic = new Cartographic();

function distanceDisplayConditionVisible(model, frameState) {
    var distance2;
    var ddc = model.distanceDisplayCondition;
    var nearSquared = ddc.near * ddc.near;
    var farSquared = ddc.far * ddc.far;

    if (frameState.mode === SceneMode.SCENE2D) {
        var frustum2DWidth = frameState.camera.frustum.right - frameState.camera.frustum.left;
        distance2 = frustum2DWidth * 0.5;
        distance2 = distance2 * distance2;
    } else {
        // Distance to center of primitive's reference frame
        var position = Matrix4.getTranslation(model.modelMatrix, scratchDisplayConditionCartesian);
        if (frameState.mode === SceneMode.COLUMBUS_VIEW) {
            var projection = frameState.mapProjection;
            var ellipsoid = projection.ellipsoid;
            var cartographic = ellipsoid.cartesianToCartographic(position, scratchDistanceDisplayConditionCartographic);
            position = projection.project(cartographic, position);
            Cartesian3.fromElements(position.z, position.x, position.y, position);
        }
        distance2 = Cartesian3.distanceSquared(position, frameState.camera.positionWC);
    }

    return (distance2 >= nearSquared) && (distance2 <= farSquared);
}


function destroyIfNotCached(rendererResources, cachedRendererResources) {
    if (rendererResources.programs !== cachedRendererResources.programs) {
        destroy(rendererResources.programs);
    }
    if (rendererResources.silhouettePrograms !== cachedRendererResources.silhouettePrograms) {
        destroy(rendererResources.silhouettePrograms);
    }
}

// Run from update iff:
// - everything is loaded
// - clipping planes state change OR color state set
// Run this from destructor after removing color state and clipping plane state
function regenerateShaders(model, frameState) {
    // In regards to _cachedRendererResources:
    // Fair to assume that this is data that should just never get modified due to clipping planes or model color.
    // So if clipping planes or model color active:
    // - delink _rendererResources.*programs and create new dictionaries.
    // - do NOT destroy any programs - might be used by copies of the model or by might be needed in the future if clipping planes/model color is deactivated

    // If clipping planes and model color inactive:
    // - destroy _rendererResources.*programs
    // - relink _rendererResources.*programs to _cachedRendererResources

    // In both cases, need to mark commands as dirty, re-run derived commands (elsewhere)

    var rendererResources = model._rendererResources;
    var cachedRendererResources = model._cachedRendererResources;
    destroyIfNotCached(rendererResources, cachedRendererResources);

    var programId;
    if (isClippingEnabled(model) || isColorShadingEnabled(model) || model._shouldRegenerateShaders) {
        model._shouldRegenerateShaders = false;

        rendererResources.programs = {};
        rendererResources.silhouettePrograms = {};

        var visitedPrograms = {};
        var techniques = model._sourceTechniques;
        var technique;

        for (var techniqueId in techniques) {
            if (techniques.hasOwnProperty(techniqueId)) {
                technique = techniques[techniqueId];
                programId = technique.program;
                if (!visitedPrograms[programId]) {
                    visitedPrograms[programId] = true;
                    recreateProgram({
                        programId: programId,
                        techniqueId: techniqueId
                    }, model, frameState.context);
                }
            }
        }
    } else {
        rendererResources.programs = cachedRendererResources.programs;
        rendererResources.silhouettePrograms = cachedRendererResources.silhouettePrograms;
    }

    // Fix all the commands, marking them as dirty so everything that derives will re-derive
    var rendererPrograms = rendererResources.programs;

    var nodeCommands = model._nodeCommands;
    var commandCount = nodeCommands.length;
    for (var i = 0; i < commandCount; ++i) {
        var nodeCommand = nodeCommands[i];
        programId = nodeCommand.programId;

        var renderProgram = rendererPrograms[programId];
        nodeCommand.command.shaderProgram = renderProgram;
        if (defined(nodeCommand.command2D)) {
            nodeCommand.command2D.shaderProgram = renderProgram;
        }
    }

    // Force update silhouette commands/shaders
    updateColor(model, frameState, true);
    updateSilhouette(model, frameState, true);
}








/**
 *
 *更新uniforms
 *
 *
 **/
function updateUniforms(model,context,fs){
    let newIndex = model._pointLight.length;
    //console.log(newIndex,model._pointLight);

    var silhouette = hasSilhouette(model, fs);
    var translucent = isTranslucent(model);

    var nodeCommands = model._nodeCommands;
    var length = nodeCommands.length;
    for (let i in nodeCommands) {
        let nc = nodeCommands[i];
        var command = translucent ? nc.translucentCommand : nc.command;
        command = silhouette ? nc.silhouetteModelCommand : command;
        let um = command.uniformMap;
        for(let j = 0;j < newIndex;j++){//let j
            um['xh_pointLightPosition' + j] = function(){
                //console.log(j,model._pointLight[j]);
                return Matrix4.multiplyByPoint( context.uniformState._view3D,model._pointLight[j],new Cartesian3);
            };
        }
    }
}

class ModelExt{
    constructor() {
        let prototype =Cesium.Model.prototype;
        Cesium.Model = (function (_super) {
                return function (options){
                    _super.bind(this)(options);

                    this._showFS = options.showFS||false;
                    this._showVS = options.showVS||false;
                    this._vertexShader = options.vertexShader||'';//顶点着色器
                    this._vertexShaderLast = options.vertexShader||'';//顶点着色器
                    this._fragmentShader = options.fragmentShader||'';//片源着色器
                    this._fragmentShaderLast = options.fragmentShader||'';//片源着色器
                    this._pointLight = options.pointLight||[];//点光源参数
                    this._pointLightLast = 0;

                    this._lightOptions = options.lightOptions||undefined;//配置灯光效果
                    if(defined(this._lightOptions)){
                        this._vertexShader = this._lightOptions.vertexShader;
                        this._fragmentShader = this._lightOptions.fragmentShader;
                        this._pointLight = this._lightOptions.plp;
                    }else{
                        this._vertexShader = '';
                        this._fragmentShader = '';
                        this._pointLight = [];
                    }
                }
            })(Cesium.Model.prototype.constructor);
        Cesium.Model.prototype = prototype;



        Cesium.Model.prototype.update = function (frameState) {
            if (frameState.mode === SceneMode.MORPHING) {
                return;
            }

            if (!FeatureDetection.supportsWebP.initialized) {
                FeatureDetection.supportsWebP.initialize();
                return;
            }
            var supportsWebP = FeatureDetection.supportsWebP();

            var context = frameState.context;
            this._defaultTexture = context.defaultTexture;

            if ((this._state === ModelState.NEEDS_LOAD) && defined(this.gltf)) {
                // Use renderer resources from cache instead of loading/creating them?
                var cachedRendererResources;
                var cacheKey = this.cacheKey;
                if (defined(cacheKey)) { // cache key given? this model will pull from or contribute to context level cache
                    context.cache.modelRendererResourceCache = defaultValue(context.cache.modelRendererResourceCache, {});
                    var modelCaches = context.cache.modelRendererResourceCache;

                    cachedRendererResources = modelCaches[this.cacheKey];
                    if (defined(cachedRendererResources)) {
                        if (!cachedRendererResources.ready) {
                            // Cached resources for the model are not loaded yet.  We'll
                            // try again every frame until they are.
                            return;
                        }

                        ++cachedRendererResources.count;
                        this._loadRendererResourcesFromCache = true;
                    } else {
                        cachedRendererResources = new CachedRendererResources(context, cacheKey);
                        cachedRendererResources.count = 1;
                        modelCaches[this.cacheKey] = cachedRendererResources;
                    }
                    this._cachedRendererResources = cachedRendererResources;
                } else { // cache key not given? this model doesn't care about context level cache at all. Cache is here to simplify freeing on destroy.
                    cachedRendererResources = new CachedRendererResources(context);
                    cachedRendererResources.count = 1;
                    this._cachedRendererResources = cachedRendererResources;
                }

                this._state = ModelState.LOADING;
                if (this._state !== ModelState.FAILED) {
                    var extensions = this.gltf.extensions;
                    if (defined(extensions) && defined(extensions.CESIUM_RTC)) {
                        var center = Cartesian3.fromArray(extensions.CESIUM_RTC.center);
                        if (!Cartesian3.equals(center, Cartesian3.ZERO)) {
                            this._rtcCenter3D = center;

                            var projection = frameState.mapProjection;
                            var ellipsoid = projection.ellipsoid;
                            var cartographic = ellipsoid.cartesianToCartographic(this._rtcCenter3D);
                            var projectedCart = projection.project(cartographic);
                            Cartesian3.fromElements(projectedCart.z, projectedCart.x, projectedCart.y, projectedCart);
                            this._rtcCenter2D = projectedCart;

                            this._rtcCenterEye = new Cartesian3();
                            this._rtcCenter = this._rtcCenter3D;
                        }
                    }

                    addPipelineExtras(this.gltf);

                    this._loadResources = new ModelLoadResources();
                    if (!this._loadRendererResourcesFromCache) {
                        // Buffers are required to updateVersion
                        ModelUtility.parseBuffers(this, bufferLoad);
                    }
                }
            }

            var loadResources = this._loadResources;
            var incrementallyLoadTextures = this._incrementallyLoadTextures;
            var justLoaded = false;

            if (this._state === ModelState.LOADING) {
                // Transition from LOADING -> LOADED once resources are downloaded and created.
                // Textures may continue to stream in while in the LOADED state.
                if (loadResources.pendingBufferLoads === 0) {
                    if (!loadResources.initialized) {
                        frameState.brdfLutGenerator.update(frameState);

                        ModelUtility.checkSupportedExtensions(this.extensionsRequired, supportsWebP);
                        ModelUtility.updateForwardAxis(this);

                        // glTF pipeline updates, not needed if loading from cache
                        if (!defined(this.gltf.extras.sourceVersion)) {
                            var gltf = this.gltf;
                            // Add the original version so it remains cached
                            gltf.extras.sourceVersion = ModelUtility.getAssetVersion(gltf);
                            gltf.extras.sourceKHRTechniquesWebGL = defined(ModelUtility.getUsedExtensions(gltf).KHR_techniques_webgl);

                            this._sourceVersion = gltf.extras.sourceVersion;
                            this._sourceKHRTechniquesWebGL = gltf.extras.sourceKHRTechniquesWebGL;

                            updateVersion(gltf);
                            addDefaults(gltf);

                            var options = {
                                addBatchIdToGeneratedShaders: this._addBatchIdToGeneratedShaders
                            };

                            processModelMaterialsCommon(gltf, options);
                            processPbrMaterials(gltf, options);
                        }

                        this._sourceVersion = this.gltf.extras.sourceVersion;
                        this._sourceKHRTechniquesWebGL = this.gltf.extras.sourceKHRTechniquesWebGL;

                        // Skip dequantizing in the shader if not encoded
                        this._dequantizeInShader = this._dequantizeInShader && DracoLoader.hasExtension(this);

                        // We do this after to make sure that the ids don't change
                        addBuffersToLoadResources(this);
                        parseArticulations(this);
                        parseTechniques(this);
                        if (!this._loadRendererResourcesFromCache) {
                            parseBufferViews(this);
                            parseShaders(this);
                            parsePrograms(this);
                            parseTextures(this, context, supportsWebP);
                        }
                        parseMaterials(this);
                        parseMeshes(this);
                        parseNodes(this);

                        // Start draco decoding
                        DracoLoader.parse(this, context);

                        loadResources.initialized = true;
                    }

                    if (!loadResources.finishedDecoding()) {
                        DracoLoader.decodeModel(this, context)
                            .otherwise(ModelUtility.getFailedLoadFunction(this, 'model', this.basePath));
                    }

                    if (loadResources.finishedDecoding() && !loadResources.resourcesParsed) {
                        this._boundingSphere = ModelUtility.computeBoundingSphere(this);
                        this._initialRadius = this._boundingSphere.radius;

                        DracoLoader.cacheDataForModel(this);

                        loadResources.resourcesParsed = true;
                    }

                    if (loadResources.resourcesParsed &&
                        loadResources.pendingShaderLoads === 0) {
                        createResources(this, frameState);
                    }
                }

                if (loadResources.finished() ||
                    (incrementallyLoadTextures && loadResources.finishedEverythingButTextureCreation())) {
                    this._state = ModelState.LOADED;
                    justLoaded = true;
                }
            }

            // Incrementally stream textures.
            if (defined(loadResources) && (this._state === ModelState.LOADED)) {
                if (incrementallyLoadTextures && !justLoaded) {
                    createResources(this, frameState);
                }

                if (loadResources.finished()) {
                    this._loadResources = undefined;  // Clear CPU memory since WebGL resources were created.

                    var resources = this._rendererResources;
                    var cachedResources = this._cachedRendererResources;

                    cachedResources.buffers = resources.buffers;
                    cachedResources.vertexArrays = resources.vertexArrays;
                    cachedResources.programs = resources.programs;
                    cachedResources.sourceShaders = resources.sourceShaders;
                    cachedResources.silhouettePrograms = resources.silhouettePrograms;
                    cachedResources.textures = resources.textures;
                    cachedResources.samplers = resources.samplers;
                    cachedResources.renderStates = resources.renderStates;
                    cachedResources.ready = true;

                    // The normal attribute name is required for silhouettes, so get it before the gltf JSON is released
                    this._normalAttributeName = ModelUtility.getAttributeOrUniformBySemantic(this.gltf, 'NORMAL');

                    // Vertex arrays are unique to this model, do not store in cache.
                    if (defined(this._precreatedAttributes)) {
                        cachedResources.vertexArrays = {};
                    }

                    if (this.releaseGltfJson) {
                        releaseCachedGltf(this);
                    }
                }
            }

            var iblSupported = OctahedralProjectedCubeMap.isSupported(context);
            if (this._shouldUpdateSpecularMapAtlas && iblSupported) {
                this._shouldUpdateSpecularMapAtlas = false;
                this._specularEnvironmentMapAtlas = this._specularEnvironmentMapAtlas && this._specularEnvironmentMapAtlas.destroy();
                this._specularEnvironmentMapAtlas = undefined;
                if (defined(this._specularEnvironmentMaps)) {
                    this._specularEnvironmentMapAtlas = new OctahedralProjectedCubeMap(this._specularEnvironmentMaps);
                    var that = this;
                    this._specularEnvironmentMapAtlas.readyPromise
                        .then(function() {
                            that._shouldRegenerateShaders = true;
                        })
                        .otherwise(function(error) {
                            console.error('Error loading specularEnvironmentMaps: ' + error);
                        });
                }

                // Regenerate shaders to not use an environment map. Will be set to true again if there was a new environment map and it is ready.
                this._shouldRegenerateShaders = true;
            }

            if (defined(this._specularEnvironmentMapAtlas)) {
                this._specularEnvironmentMapAtlas.update(frameState);
            }

            var recompileWithDefaultAtlas = !defined(this._specularEnvironmentMapAtlas) && defined(frameState.specularEnvironmentMaps) && !this._useDefaultSpecularMaps;
            var recompileWithoutDefaultAtlas = !defined(frameState.specularEnvironmentMaps) && this._useDefaultSpecularMaps;

            var recompileWithDefaultSHCoeffs = !defined(this._sphericalHarmonicCoefficients) && defined(frameState.sphericalHarmonicCoefficients) && !this._useDefaultSphericalHarmonics;
            var recompileWithoutDefaultSHCoeffs = !defined(frameState.sphericalHarmonicCoefficients) && this._useDefaultSphericalHarmonics;

            this._shouldRegenerateShaders = this._shouldRegenerateShaders || recompileWithDefaultAtlas || recompileWithoutDefaultAtlas || recompileWithDefaultSHCoeffs || recompileWithoutDefaultSHCoeffs;

            this._useDefaultSpecularMaps = !defined(this._specularEnvironmentMapAtlas) && defined(frameState.specularEnvironmentMaps);
            this._useDefaultSphericalHarmonics = !defined(this._sphericalHarmonicCoefficients) && defined(frameState.sphericalHarmonicCoefficients);

            var silhouette = hasSilhouette(this, frameState);
            var translucent = isTranslucent(this);
            var invisible = isInvisible(this);
            var displayConditionPassed = defined(this.distanceDisplayCondition) ? distanceDisplayConditionVisible(this, frameState) : true;
            var show = this.show && displayConditionPassed && (this.scale !== 0.0) && (!invisible || silhouette);

            if ((show && this._state === ModelState.LOADED) || justLoaded) {
                var animated = this.activeAnimations.update(frameState) || this._cesiumAnimationsDirty;
                this._cesiumAnimationsDirty = false;
                this._dirty = false;
                var modelMatrix = this.modelMatrix;

                var modeChanged = frameState.mode !== this._mode;
                this._mode = frameState.mode;

                // Model's model matrix needs to be updated
                var modelTransformChanged = !Matrix4.equals(this._modelMatrix, modelMatrix) ||
                    (this._scale !== this.scale) ||
                    (this._minimumPixelSize !== this.minimumPixelSize) || (this.minimumPixelSize !== 0.0) || // Minimum pixel size changed or is enabled
                    (this._maximumScale !== this.maximumScale) ||
                    (this._heightReference !== this.heightReference) || this._heightChanged ||
                    modeChanged;

                if (modelTransformChanged || justLoaded) {
                    Matrix4.clone(modelMatrix, this._modelMatrix);

                    updateClamping(this);

                    if (defined(this._clampedModelMatrix)) {
                        modelMatrix = this._clampedModelMatrix;
                    }

                    this._scale = this.scale;
                    this._minimumPixelSize = this.minimumPixelSize;
                    this._maximumScale = this.maximumScale;
                    this._heightReference = this.heightReference;
                    this._heightChanged = false;

                    var scale = getScale(this, frameState);
                    var computedModelMatrix = this._computedModelMatrix;
                    Matrix4.multiplyByUniformScale(modelMatrix, scale, computedModelMatrix);
                    if (this._upAxis === Axis.Y) {
                        Matrix4.multiplyTransformation(computedModelMatrix, Axis.Y_UP_TO_Z_UP, computedModelMatrix);
                    } else if (this._upAxis === Axis.X) {
                        Matrix4.multiplyTransformation(computedModelMatrix, Axis.X_UP_TO_Z_UP, computedModelMatrix);
                    }
                    if (this.forwardAxis === Axis.Z) {
                        // glTF 2.0 has a Z-forward convention that must be adapted here to X-forward.
                        Matrix4.multiplyTransformation(computedModelMatrix, Axis.Z_UP_TO_X_UP, computedModelMatrix);
                    }
                }

                // Update modelMatrix throughout the graph as needed
                if (animated || modelTransformChanged || justLoaded) {
                    updateNodeHierarchyModelMatrix(this, modelTransformChanged, justLoaded, frameState.mapProjection);
                    this._dirty = true;

                    if (animated || justLoaded) {
                        // Apply skins if animation changed any node transforms
                        applySkins(this);
                    }
                }

                if (this._perNodeShowDirty) {
                    this._perNodeShowDirty = false;
                    updatePerNodeShow(this);
                }
                updatePickIds(this, context);
                updateWireframe(this);
                updateShowBoundingVolume(this);
                updateShadows(this);
                updateClippingPlanes(this, frameState);

                // Regenerate shaders if ClippingPlaneCollection state changed or it was removed
                var clippingPlanes = this._clippingPlanes;
                var currentClippingPlanesState = 0;
                var useClippingPlanes = defined(clippingPlanes) && clippingPlanes.enabled && clippingPlanes.length > 0;
                var usesSH = defined(this._sphericalHarmonicCoefficients) || this._useDefaultSphericalHarmonics;
                var usesSM = (defined(this._specularEnvironmentMapAtlas) && this._specularEnvironmentMapAtlas.ready) || this._useDefaultSpecularMaps;
                if (useClippingPlanes || usesSH || usesSM) {
                    var clippingPlanesOriginMatrix = defaultValue(this.clippingPlanesOriginMatrix, modelMatrix);
                    Matrix4.multiply(context.uniformState.view3D, clippingPlanesOriginMatrix, this._clippingPlaneModelViewMatrix);
                }

                if (useClippingPlanes) {
                    currentClippingPlanesState = clippingPlanes.clippingPlanesState;
                }

                var shouldRegenerateShaders = this._shouldRegenerateShaders;
                shouldRegenerateShaders = shouldRegenerateShaders || this._clippingPlanesState !== currentClippingPlanesState;
                this._clippingPlanesState = currentClippingPlanesState;

                // Regenerate shaders if color shading changed from last update
                var currentlyColorShadingEnabled = isColorShadingEnabled(this);
                if (currentlyColorShadingEnabled !== this._colorShadingEnabled) {
                    this._colorShadingEnabled = currentlyColorShadingEnabled;
                    shouldRegenerateShaders = true;
                }

                //更新shader
                //console.log(this._vertexShaderLast,this._framentShaderLast)
                if(this._fragmentShaderLast === undefined){
                    this._fragmentShaderLast ='';
                }
                if(this._vertexShaderLast === undefined){
                    this._vertexShaderLast = '';
                }
                if((this._fragmentShader && this._fragmentShader.length !== this._fragmentShaderLast.length)||(this._vertexShader && this._vertexShader.length !== this._vertexShaderLast.length) ){
                    this._fragmentShaderLast = this._fragmentShader;
                    this._vertexShaderLast = this._vertexShader;
                    this._shouldRegenerateShaders = true;//更新shader
                }

                if (shouldRegenerateShaders) {
                    regenerateShaders(this, frameState);
                } else {
                    updateColor(this, frameState, false);
                    updateSilhouette(this, frameState, false);
                }

                //更新uniforms
                if(this._pointLight && this._pointLight.length > 0 && this._pointLight.length !==this._pointLightLast){
                    updateUniforms(this,frameState.context,frameState);
                    this._pointLightLast = this._pointLight.length;
                }
            }

            if (justLoaded) {
                // Called after modelMatrix update.
                var model = this;
                frameState.afterRender.push(function() {
                    model._ready = true;
                    model._readyPromise.resolve(model);
                });
                return;
            }

            // We don't check show at the top of the function since we
            // want to be able to progressively load models when they are not shown,
            // and then have them visible immediately when show is set to true.
            if (show && !this._ignoreCommands) {
                // PERFORMANCE_IDEA: This is terrible
                var commandList = frameState.commandList;
                var passes = frameState.passes;
                var nodeCommands = this._nodeCommands;
                var length = nodeCommands.length;
                var i;
                var nc;

                var idl2D = frameState.mapProjection.ellipsoid.maximumRadius * CesiumMath.PI;
                var boundingVolume;

                if (passes.render || (passes.pick && this.allowPicking)) {
                    for (i = 0; i < length; ++i) {
                        nc = nodeCommands[i];
                        if (nc.show) {
                            var command = translucent ? nc.translucentCommand : nc.command;
                            command = silhouette ? nc.silhouetteModelCommand : command;
                            commandList.push(command);
                            boundingVolume = nc.command.boundingVolume;
                            if (frameState.mode === SceneMode.SCENE2D &&
                                (boundingVolume.center.y + boundingVolume.radius > idl2D || boundingVolume.center.y - boundingVolume.radius < idl2D)) {
                                var command2D = translucent ? nc.translucentCommand2D : nc.command2D;
                                command2D = silhouette ? nc.silhouetteModelCommand2D : command2D;
                                commandList.push(command2D);
                            }
                        }
                    }

                    if (silhouette && !passes.pick) {
                        // Render second silhouette pass
                        for (i = 0; i < length; ++i) {
                            nc = nodeCommands[i];
                            if (nc.show) {
                                commandList.push(nc.silhouetteColorCommand);
                                boundingVolume = nc.command.boundingVolume;
                                if (frameState.mode === SceneMode.SCENE2D &&
                                    (boundingVolume.center.y + boundingVolume.radius > idl2D || boundingVolume.center.y - boundingVolume.radius < idl2D)) {
                                    commandList.push(nc.silhouetteColorCommand2D);
                                }
                            }
                        }
                    }
                }
            }

            var credit = this._credit;
            if (defined(credit)) {
                frameState.creditDisplay.addCredit(credit);
            }

            var resourceCredits = this._resourceCredits;
            var creditCount = resourceCredits.length;
            for (var c = 0; c < creditCount; c++) {
                frameState.creditDisplay.addCredit(resourceCredits[c]);
            }
        };



        /**
         * <p>
         * Creates a model from a glTF asset.  When the model is ready to render, i.e., when the external binary, image,
         * and shader files are downloaded and the WebGL resources are created, the {@link Model#readyPromise} is resolved.
         * </p>
         * <p>
         * The model can be a traditional glTF asset with a .gltf extension or a Binary glTF using the .glb extension.
         * </p>
         * <p>
         * Cesium supports glTF assets with the following extensions:
         * <ul>
         * <li>
         * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/1.0/Khronos/KHR_binary_glTF/README.md|KHR_binary_glTF (glTF 1.0)}
         * </li><li>
         * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/1.0/Khronos/KHR_materials_common/README.md|KHR_materials_common (glTF 1.0)}
         * </li><li>
         * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/1.0/Vendor/WEB3D_quantized_attributes/README.md|WEB3D_quantized_attributes (glTF 1.0)}
         * </li><li>
         * {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/AGI_articulations/README.md|AGI_articulations}
         * </li><li>
         * {@link https://github.com/KhronosGroup/glTF/pull/1302|KHR_blend (draft)}
         * </li><li>
         * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_draco_mesh_compression/README.md|KHR_draco_mesh_compression}
         * </li><li>
         * {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/README.md|KHR_materials_pbrSpecularGlossiness}
         * </li><li>
         * {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit/README.md|KHR_materials_unlit}
         * </li><li>
         * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_techniques_webgl/README.md|KHR_techniques_webgl}
         * </li><li>
         * {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_texture_transform/README.md|KHR_texture_transform}
         * </li>
         * </ul>
         * </p>
         * <p>
         * For high-precision rendering, Cesium supports the {@link https://github.com/KhronosGroup/glTF/blob/master/extensions/1.0/Vendor/CESIUM_RTC/README.md|CESIUM_RTC} extension, which introduces the
         * CESIUM_RTC_MODELVIEW parameter semantic that says the node is in WGS84 coordinates translated
         * relative to a local origin.
         * </p>
         *
         * @param {Object} options Object with the following properties:
         * @param {Resource|String} options.url The url to the .gltf file.
         * @param {Resource|String} [options.basePath] The base path that paths in the glTF JSON are relative to.
         * @param {Boolean} [options.show=true] Determines if the model primitive will be shown.
         * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the model from model to world coordinates.
         * @param {Number} [options.scale=1.0] A uniform scale applied to this model.
         * @param {Number} [options.minimumPixelSize=0.0] The approximate minimum pixel size of the model regardless of zoom.
         * @param {Number} [options.maximumScale] The maximum scale for the model.
         * @param {Object} [options.id] A user-defined object to return when the model is picked with {@link Scene#pick}.
         * @param {Boolean} [options.allowPicking=true] When <code>true</code>, each glTF mesh and primitive is pickable with {@link Scene#pick}.
         * @param {Boolean} [options.incrementallyLoadTextures=true] Determine if textures may continue to stream in after the model is loaded.
         * @param {Boolean} [options.asynchronous=true] Determines if model WebGL resource creation will be spread out over several frames or block until completion once all glTF files are loaded.
         * @param {Boolean} [options.clampAnimations=true] Determines if the model's animations should hold a pose over frames where no keyframes are specified.
         * @param {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the model casts or receives shadows from light sources.
         * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for each draw command in the model.
         * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the model in wireframe.
         * @param {HeightReference} [options.heightReference=HeightReference.NONE] Determines how the model is drawn relative to terrain.
         * @param {Scene} [options.scene] Must be passed in for models that use the height reference property.
         * @param {DistanceDisplayCondition} [options.distanceDisplayCondition] The condition specifying at what distance from the camera that this model will be displayed.
         * @param {Color} [options.color=Color.WHITE] A color that blends with the model's rendered color.
         * @param {ColorBlendMode} [options.colorBlendMode=ColorBlendMode.HIGHLIGHT] Defines how the color blends with the model.
         * @param {Number} [options.colorBlendAmount=0.5] Value used to determine the color strength when the <code>colorBlendMode</code> is <code>MIX</code>. A value of 0.0 results in the model's rendered color while a value of 1.0 results in a solid color, with any value in-between resulting in a mix of the two.
         * @param {Color} [options.silhouetteColor=Color.RED] The silhouette color. If more than 256 models have silhouettes enabled, there is a small chance that overlapping models will have minor artifacts.
         * @param {Number} [options.silhouetteSize=0.0] The size of the silhouette in pixels.
         * @param {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the model.
         * @param {Boolean} [options.dequantizeInShader=true] Determines if a {@link https://github.com/google/draco|Draco} encoded model is dequantized on the GPU. This decreases total memory usage for encoded models.
         * @param {Credit|String} [options.credit] A credit for the model, which is displayed on the canvas.
         *
         * @returns {Model} The newly created model.
         *
         * @example
         * // Example 1. Create a model from a glTF asset
         * var model = scene.primitives.add(Cesium.Model.fromGltf({
     *   url : './duck/duck.gltf'
     * }));
         *
         * @example
         * // Example 2. Create model and provide all properties and events
         * var origin = Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 200000.0);
         * var modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
         *
         * var model = scene.primitives.add(Cesium.Model.fromGltf({
     *   url : './duck/duck.gltf',
     *   show : true,                     // default
     *   modelMatrix : modelMatrix,
     *   scale : 2.0,                     // double size
     *   minimumPixelSize : 128,          // never smaller than 128 pixels
     *   maximumScale: 20000,             // never larger than 20000 * model size (overrides minimumPixelSize)
     *   allowPicking : false,            // not pickable
     *   debugShowBoundingVolume : false, // default
     *   debugWireframe : false
     * }));
         *
         * model.readyPromise.then(function(model) {
     *   // Play all animations when the model is ready to render
     *   model.activeAnimations.addAll();
     * });
         */
        Cesium.Model.fromGltf = function(options) {
            //>>includeStart('debug', pragmas.debug);
            if (!defined(options) || !defined(options.url)) {
                throw new DeveloperError('options.url is required');
            }
            //>>includeEnd('debug');

            var url = options.url;
            options = clone(options);

            // Create resource for the model file
            var modelResource = Resource.createIfNeeded(url);

            // Setup basePath to get dependent files
            var basePath = defaultValue(options.basePath, modelResource.clone());
            var resource = Resource.createIfNeeded(basePath);

            // If no cache key is provided, use a GUID.
            // Check using a URI to GUID dictionary that we have not already added this model.
            var cacheKey = defaultValue(options.cacheKey, uriToGuid[getAbsoluteUri(modelResource.url)]);
            if (!defined(cacheKey)) {
                cacheKey = createGuid();
                uriToGuid[getAbsoluteUri(modelResource.url)] = cacheKey;
            }

            if (defined(options.basePath) && !defined(options.cacheKey)) {
                cacheKey += resource.url;
            }
            options.cacheKey = cacheKey;
            options.basePath = resource;

            var model = new Cesium.Model(options);

            var cachedGltf = gltfCache[cacheKey];
            if (!defined(cachedGltf)) {
                cachedGltf = new CachedGltf({
                    ready : false
                });
                cachedGltf.count = 1;
                cachedGltf.modelsToLoad.push(model);
                setCachedGltf(model, cachedGltf);
                gltfCache[cacheKey] = cachedGltf;

                // Add Accept header if we need it
                if (!defined(modelResource.headers.Accept)) {
                    modelResource.headers.Accept = defaultModelAccept;
                }

                modelResource.fetchArrayBuffer().then(function(arrayBuffer) {
                    var array = new Uint8Array(arrayBuffer);
                    if (containsGltfMagic(array)) {
                        // Load binary glTF
                        var parsedGltf = parseGlb(array);
                        cachedGltf.makeReady(parsedGltf);
                    } else {
                        // Load text (JSON) glTF
                        var json = getStringFromTypedArray(array);
                        cachedGltf.makeReady(JSON.parse(json));
                    }

                    var resourceCredits = model._resourceCredits;
                    var credits = modelResource.credits;
                    if (defined(credits)) {
                        var length = credits.length;
                        for (var i = 0; i < length; i++) {
                            resourceCredits.push(credits[i]);
                        }
                    }
                }).otherwise(ModelUtility.getFailedLoadFunction(model, 'model', modelResource.url));
            } else if (!cachedGltf.ready) {
                // Cache hit but the fetchArrayBuffer() or fetchText() request is still pending
                ++cachedGltf.count;
                cachedGltf.modelsToLoad.push(model);
            }
            // else if the cached glTF is defined and ready, the
            // model constructor will pick it up using the cache key.

            return model;
        };

        // exposed for testing
        Cesium.Model._getClippingFunction = getClippingFunction;
        Cesium.Model._modifyShaderForColor = modifyShaderForColor;

        /**
         * For the unit tests to verify model caching.
         *
         * @private
         */
        Cesium.Model._gltfCache = gltfCache;

        /**
         * Determines if silhouettes are supported.
         *
         * @param {Scene} scene The scene.
         * @returns {Boolean} <code>true</code> if silhouettes are supported; otherwise, returns <code>false</code>
         */
        Cesium.Model.silhouetteSupported = function(scene) {
            return silhouetteSupported(scene.context);
        };

    }
}
new ModelExt();
module.exports = ModelExt;
